/* Copyright 2012 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.mobilyzer.util;

import android.Manifest;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.BatteryManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Looper;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.provider.Settings.Secure;
import android.support.v4.content.ContextCompat;
import android.telephony.CellInfo;
import android.telephony.CellInfoCdma;
import android.telephony.CellInfoGsm;
import android.telephony.CellInfoLte;
import android.telephony.CellInfoWcdma;
import android.telephony.NeighboringCellInfo;
import android.telephony.PhoneStateListener;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
import android.view.Display;
import android.view.WindowManager;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteOrder;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CountDownLatch;

import com.mobilyzer.Config;
import com.mobilyzer.DeviceInfo;
import com.mobilyzer.DeviceProperty;
import com.mobilyzer.R;


/**
 * Phone related utilities.
 */
@SuppressLint("NewApi")
public class PhoneUtils {

	private static final String ANDROID_STRING = "Android";
	/** Returned by {@link #getNetwork()}. */
	public static final String NETWORK_WIFI = "Wifi";
	/** IP type */
	public static final String IP_TYPE_UNKNOWN = "UNKNOWN";
	public static final String IP_TYPE_NONE = "Neither IPv4 nor IPv6";
	public static final String IP_TYPE_IPV4_ONLY = "IPv4 only";
	public static final String IP_TYPE_IPV6_ONLY = "IPv6 only";
	public static final String IP_TYPE_IPV4_IPV6_BOTH = "IPv4 and IPv6";

	public static volatile HashSet<String> clientKeySet
	= new HashSet<String>();
	/**
	 * The app that uses this class. The app must remain alive for longer than
	 * PhoneUtils objects are in use.
	 *
	 * @see #setGlobalContext(Context)
	 */
	private static Context globalContext = null;

	/** A singleton instance of PhoneUtils. */
	private static PhoneUtils singletonPhoneUtils = null;

	/** Phone context object giving access to various phone parameters. */
	private Context context = null;

	/** Allows to obtain the phone's location, to determine the country. */
	private LocationManager locationManager = null;

	/** The name of the location provider with "coarse" precision (cell/wifi). */
	private String locationProviderName = null;

	/** Allows to disable going to low-power mode where WiFi gets turned off. */
	WakeLock wakeLock = null;

	/** Call initNetworkManager() before using this var. */
	private ConnectivityManager connectivityManager = null;

	/** Call initNetworkManager() before using this var. */
	private TelephonyManager telephonyManager = null;

	/** Tells whether the phone is charging */
	private boolean isCharging;
	/** Current battery level in percentage */ 
	private int curBatteryLevel;
	/** Receiver that handles battery change broadcast intents */
	private BroadcastReceiver broadcastReceiver;
	private String currentSignalStrength = "UNKNOWN";

	/** For monitoring the current network connection type**/
	public static int TYPE_WIFI = 1;
	public static int TYPE_MOBILE = 2;
	public static int TYPE_NOT_CONNECTED = 0;
	private int currentNetworkConnection= TYPE_NOT_CONNECTED; 


	private DeviceInfo deviceInfo = null;
	/** IP compatibility status */
	// Indeterministic type due to client side timer expired
	private int IP_TYPE_CANNOT_DECIDE = 2;
	// Cannot resolve the hostname or cannot reach the destination address
	private int IP_TYPE_UNCONNECTIVITY = 1;
	private int IP_TYPE_CONNECTIVITY = 0; 
	/** Domain name resolution status */
	private int DN_UNKNOWN = 2;
	private int DN_UNRESOLVABLE = 1;
	private int DN_RESOLVABLE = 0;
	//server configuration port on M-Lab servers 
	private int portNum = 6003;
	private int tcpTimeout = 3000;


	private ConnectivityManager.NetworkCallback connectivityNetworkCallback = null;

	protected PhoneUtils(Context context) {
		this.context = context;
		broadcastReceiver = new PowerStateChangeReceiver();
		// Registers a receiver for battery change events.
		Intent powerIntent = globalContext.registerReceiver(broadcastReceiver, 
				new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
		updateBatteryStat(powerIntent);
	}

	/**
	 * The owner app class must call this method from its onCreate(), before
	 * getPhoneUtils().
	 */
	public static synchronized void setGlobalContext(Context newGlobalContext) {
		assert newGlobalContext != null;
		assert singletonPhoneUtils == null;  // Should not yet be created
		// Not supposed to change the owner app
		assert globalContext == null || globalContext == newGlobalContext;

		globalContext = newGlobalContext;
	}

	public static synchronized void releaseGlobalContext() {
		globalContext = null;
		singletonPhoneUtils = null;
	}

	/** Returns the context previously set with {@link #setGlobalContext}. */
	public static synchronized Context getGlobalContext() {
		assert globalContext != null;
		return globalContext;
	}

	/**
	 * Returns a singleton instance of PhoneUtils. The caller must call
	 * {@link #setGlobalContext(Context)} before calling this method.
	 */
	public static synchronized PhoneUtils getPhoneUtils() {

		if (singletonPhoneUtils == null) {
			assert globalContext != null;
			singletonPhoneUtils = new PhoneUtils(globalContext);
		}

		return singletonPhoneUtils;
	}

	/**
	 * Returns a string representing this phone:
	 *
	 * "Android_<hardware-type>-<build-release>_<network-type>_" +
	 * "<network-carrier>_<mobile-type>_<Portrait-or-Landscape>"
	 *
	 * hardware-type is e.g. "dream", "passion", "emulator", etc.
	 * build-release is the SDK public release number e.g. "2.0.1" for Eclair.
	 * network-type is e.g. "Wifi", "Edge", "UMTS", "3G".
	 * network-carrier is the mobile carrier name if connected via the SIM card,
	 *     or the Wi-Fi SSID if connected via the Wi-Fi.
	 * mobile-type is the phone's mobile network connection type -- "GSM" or "CDMA".
	 *
	 * If the device screen is currently in lanscape mode, "_Landscape" is
	 * appended at the end.
	 *
	 * TODO(klm): This needs to be converted into named URL args from positional,
	 * both here and in the iPhone app. Otherwise it's hard to add extensions,
	 * especially if there is optional stuff like
	 *
	 * @return a string representing this phone
	 */
	public String generatePhoneId() {
		String device = Build.DEVICE.equals("generic") ? "emulator" : Build.DEVICE;
		String network = getNetwork();
		String carrier = (network == NETWORK_WIFI) ?
				getWifiCarrierName() : getTelephonyCarrierName();

				StringBuilder stringBuilder = new StringBuilder(ANDROID_STRING);
				stringBuilder.append('-').append(device).append('_')
				.append(Build.VERSION.RELEASE).append('_').append(network)
				.append('_').append(carrier).append('_').append(getTelephonyPhoneType())
				.append('_').append(isLandscape() ? "Landscape" : "Portrait");

				return stringBuilder.toString();
	}

	/**
	 * Lazily initializes the network managers.
	 *
	 * As a side effect, assigns connectivityManager and telephonyManager.
	 */
	private synchronized void initNetwork() {
		if (connectivityManager == null) {
			ConnectivityManager tryConnectivityManager =
					(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

			TelephonyManager tryTelephonyManager =
					(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);

			// Assign to member vars only after all the get calls succeeded,
			// so that either all get assigned, or none get assigned.
			connectivityManager = tryConnectivityManager;
			telephonyManager = tryTelephonyManager;

			// Some interesting info to look at in the logs
			NetworkInfo[] infos = connectivityManager.getAllNetworkInfo();
			for (NetworkInfo networkInfo : infos) {
				Logger.i("Network: " + networkInfo);
			}
			Logger.i("Phone type: " + getTelephonyPhoneType() +
					", Carrier: " + getTelephonyCarrierName());
		}
		assert connectivityManager != null;
		assert telephonyManager != null;
	}

	/**
	 * This method must be called in the service thread, as the system will create a Looper in
	 * the calling thread which will handle the callbacks.
	 */
	public void registerSignalStrengthListener() {
		initNetwork();
		telephonyManager.listen(new SignalStrengthChangeListener(), 
				PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
	}

	/** Returns the network that the phone is on (e.g. Wifi, Edge, GPRS, etc). */
	public String getNetwork() {
		initNetwork();
		NetworkInfo networkInfo =
				connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
		if (networkInfo != null &&
				networkInfo.getState() == NetworkInfo.State.CONNECTED) {
			Logger.d("Current Network: WIFI");
			return NETWORK_WIFI;
		} else {
			return getTelephonyNetworkType();
		}
	}
	/** Detect whether there is an Internet connection available */
	public boolean isNetworkAvailable() {
		initNetwork();
		NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
		return activeNetworkInfo != null && activeNetworkInfo.isConnected();
	}

	//  /** Returns the WiFi network state (NetworkInfo.State) */
	//  public NetworkInfo.State getNetworkState() {
	//    initNetwork();
	//    NetworkInfo networkInfo =
	//      connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
	//    return networkInfo.getState();
	//  }

	private static final String[] NETWORK_TYPES = {
		"UNKNOWN",  // 0  - NETWORK_TYPE_UNKNOWN
		"GPRS",     // 1  - NETWORK_TYPE_GPRS
		"EDGE",     // 2  - NETWORK_TYPE_EDGE
		"UMTS",     // 3  - NETWORK_TYPE_UMTS
		"CDMA",     // 4  - NETWORK_TYPE_CDMA
		"EVDO_0",   // 5  - NETWORK_TYPE_EVDO_0
		"EVDO_A",   // 6  - NETWORK_TYPE_EVDO_A
		"1xRTT",    // 7  - NETWORK_TYPE_1xRTT
		"HSDPA",    // 8  - NETWORK_TYPE_HSDPA
		"HSUPA",    // 9  - NETWORK_TYPE_HSUPA
		"HSPA",     // 10 - NETWORK_TYPE_HSPA
		"IDEN",     // 11 - NETWORK_TYPE_IDEN
		"EVDO_B",   // 12 - NETWORK_TYPE_EVDO_B
		"LTE",      // 13 - NETWORK_TYPE_LTE
		"EHRPD",    // 14 - NETWORK_TYPE_EHRPD
		"HSPAP",    // 15 - NETWORK_TYPE_HSPAP
	};

	/** Returns mobile data network connection type. */
	public String getTelephonyNetworkType() {
		assert NETWORK_TYPES[14].compareTo("EHRPD") == 0;

		int networkType = telephonyManager.getNetworkType();
		if (networkType < NETWORK_TYPES.length) {
			return NETWORK_TYPES[telephonyManager.getNetworkType()];
		} else {
			return "Unrecognized: " + networkType;
		}
	}

	/** Returns "GSM", "CDMA". */
	private String getTelephonyPhoneType() {
		switch (telephonyManager.getPhoneType()) {
		case TelephonyManager.PHONE_TYPE_CDMA:
			return "CDMA";
		case TelephonyManager.PHONE_TYPE_GSM:
			return "GSM";
		case TelephonyManager.PHONE_TYPE_NONE:
			return "None";
		}
		return "Unknown";
	}

	/** Returns current mobile phone carrier name, or empty if not connected. */
	private String getTelephonyCarrierName() {
		return telephonyManager.getNetworkOperatorName();
	}

	/** Returns current Wi-Fi SSID, or null if Wi-Fi is not connected. */
	private String getWifiCarrierName() {
		WifiManager wifiManager =
				(WifiManager) context.getSystemService(Context.WIFI_SERVICE);
		WifiInfo wifiInfo = wifiManager.getConnectionInfo();
		if (wifiInfo != null) {
			return wifiInfo.getSSID();
		}
		return null;
	}

	/**
	 * Returns the information about cell towers in range. Returns null if the information is 
	 * not available 
	 * 
	 * TODO(wenjiezeng): As folklore has it and Wenjie has confirmed, we cannot get cell info from
	 * Samsung phones.
	 */
	public String getCellInfo(boolean cidOnly) {
		if(!(ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION)==PackageManager.PERMISSION_GRANTED)){
			return null;
		}

		initNetwork();
		List<NeighboringCellInfo> infos = telephonyManager.getNeighboringCellInfo();
		StringBuffer buf = new StringBuffer();
		String tempResult = "";
		if (infos.size() > 0) {
			for (NeighboringCellInfo info : infos) {
				tempResult = cidOnly ? info.getCid() + ";" : info.getLac() + "," 
						+ info.getCid() + "," + info.getRssi() + ";";
				buf.append(tempResult);
			}
			// Removes the trailing semicolon
			buf.deleteCharAt(buf.length() - 1);
			return buf.toString();
		} else {
			return null;
		}
	}

	/**
	 * Lazily initializes the location manager.
	 *
	 * As a side effect, assigns locationManager and locationProviderName.
	 */
	private synchronized void initLocation() {
		if (locationManager == null) {
			LocationManager manager =
					(LocationManager) context.getSystemService(Context.LOCATION_SERVICE);

			Criteria criteriaCoarse = new Criteria();
			/* "Coarse" accuracy means "no need to use GPS".
			 * Typically a gShots phone would be located in a building,
			 * and GPS may not be able to acquire a location.
			 * We only care about the location to determine the country,
			 * so we don't need a super accurate location, cell/wifi is good enough.
			 */
			criteriaCoarse.setAccuracy(Criteria.ACCURACY_COARSE);
			criteriaCoarse.setPowerRequirement(Criteria.POWER_LOW);
			String providerName =
					manager.getBestProvider(criteriaCoarse, /*enabledOnly=*/true);


			List<String> providers = manager.getAllProviders();
			for (String providerNameIter : providers) {
				try {
					LocationProvider provider = manager.getProvider(providerNameIter);
				} catch (SecurityException se) {
					// Not allowed to use this provider
					Logger.w("Unable to use provider " + providerNameIter);
					continue;
				}
				Logger.i(providerNameIter + ": " +
						(manager.isProviderEnabled(providerNameIter) ? "enabled" : "disabled"));
			}

			/* Make sure the provider updates its location.
			 * Without this, we may get a very old location, even a
			 * device powercycle may not update it.
			 * {@see android.location.LocationManager.getLastKnownLocation}.
			 */
			manager.requestLocationUpdates(providerName,
					/*minTime=*/0,
					/*minDistance=*/0,
					new LoggingLocationListener(),
					Looper.getMainLooper());
			locationManager = manager;
			locationProviderName = providerName;
		}
		assert locationManager != null;
		assert locationProviderName != null;
	}

	/**
	 * Returns the location of the device.
	 *
	 * @return the location of the device
	 */
	public Location getLocation() {
		if(!(ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION)==PackageManager.PERMISSION_GRANTED)){
			return new Location("unknown");
		}
		
		try {
			initLocation();
			Location location = locationManager.getLastKnownLocation(locationProviderName);
			if (location == null) {
				Logger.e("Cannot obtain location from provider " + locationProviderName);
				return new Location("unknown");
			}
			return location;
		} catch (IllegalArgumentException e) {
			Logger.e("Cannot obtain location", e);
			return new Location("unknown");
		}
	}

	/** Wakes up the CPU of the phone if it is sleeping. */
	public synchronized void acquireWakeLock() {
		if (wakeLock == null) {
			PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
			wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "tag");
		}
		Logger.d("PowerLock acquired");
		wakeLock.acquire();
	}

	/** Release the CPU wake lock. WakeLock is reference counted by default: no need to worry
	 * about releasing someone else's wake lock */
	public synchronized void releaseWakeLock() {
		if (wakeLock != null) {
			try {
				wakeLock.release();
				Logger.i("PowerLock released");
			} catch (RuntimeException e) {
				Logger.e("Exception when releasing wakeup lock", e);
			}
		}
	}

	/** Release all resource upon app shutdown */
	public synchronized void shutDown() {
		if (this.wakeLock != null) {
			/* Wakelock are ref counted by default. We disable this feature here to ensure that
			 * the power lock is released upon shutdown.
			 */ 
			wakeLock.setReferenceCounted(false);
			wakeLock.release();
		}
		context.unregisterReceiver(broadcastReceiver);
		releaseGlobalContext();
	}

	/**
	 * Returns true if the phone is in landscape mode.
	 */
	public boolean isLandscape() {
		WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
		Display display = wm.getDefaultDisplay();
		return display.getWidth() > display.getHeight();
	}



	/**
	 * A dummy listener that just logs callbacks.
	 */
	private static class LoggingLocationListener implements LocationListener {

		@Override
		public void onLocationChanged(Location location) {
			Logger.d("location changed");
		}

		@Override
		public void onProviderDisabled(String provider) {
			Logger.d("provider disabled: " + provider);
		}

		@Override
		public void onProviderEnabled(String provider) {
			Logger.d("provider enabled: " + provider);
		}

		@Override
		public void onStatusChanged(String provider, int status, Bundle extras) {
			Logger.d("status changed: " + provider + "=" + status);
		}
	}

	/**
	 * Types of interfaces to return from {@link #getUpInterfaces(InterfaceType)}.
	 */
	public enum InterfaceType {
		/** Local and external interfaces. */
		ALL,

		/** Only external interfaces. */
		EXTERNAL_ONLY,
	}

	/** Returns a debug printable representation of a string list. */
	public static String debugString(List<String> stringList) {
		StringBuilder result = new StringBuilder("[");
		Iterator<String> listIter = stringList.iterator();
		if (listIter.hasNext()) {
			result.append('"');  // Opening quote for the first string
			result.append(listIter.next());
			while (listIter.hasNext()) {
				result.append("\", \"");
				result.append(listIter.next());
			}
			result.append('"');  // Closing quote for the last string
		}
		result.append(']');
		return result.toString();
	}

	/** Returns a debug printable representation of a string array. */
	public static String debugString(String[] arr) {
		return debugString(Arrays.asList(arr));
	}

	public String getAppVersionName() {
		try {
			String packageName = context.getPackageName();
			return context.getPackageManager().getPackageInfo(packageName, 0).versionName;
			//      String versionName = context.getString(R.string.scheduler_version_name);
			//      Logger.i("Scheduler: version name = " + versionName);
			//      return versionName;
		} catch (Exception e) {
			Logger.e("version name of the application cannot be found", e);
		}
		return "Unknown";
	}

	/**
	 * Returns the current battery level
	 * */
	public synchronized int getCurrentBatteryLevel() {
		return curBatteryLevel;
	}

	/**
	 * Returns if the batter is charing
	 */
	public synchronized boolean isCharging() {
		return isCharging;
	}

	/**
	 * Sets the current RSSI value
	 */
	public synchronized void setCurrentRssi(String rssi) {
		currentSignalStrength = rssi;
	}

	/**
	 * Returns the last updated RSSI value
	 */
	public synchronized String getCurrentRssi() {
		initNetwork();
		return currentSignalStrength;
	}
	
	public String  getCellRssi(){
		String cellRssi1=getCurrentRssi();
		String cellRssi2="";
		HashMap<String, Integer> cellInfosMap=getAllCellInfoSignalStrength();
		if (cellInfosMap!=null) {
			for (String cinfo : cellInfosMap.keySet()) {
				cellRssi2 += cinfo + ":" + cellInfosMap.get(cinfo) + "|";
			}
		}
        
        return cellRssi1+cellRssi2;
		
	}
	
	
	

	public String getWifiBSSID(){
		WifiManager wifiManager =
				(WifiManager) context.getSystemService(Context.WIFI_SERVICE);
		WifiInfo wifiInfo = wifiManager.getConnectionInfo();
		if (wifiInfo != null) {
			return wifiInfo.getBSSID();
		}
		return null;
	}
	
	public String getWifiSSID(){
		WifiManager wifiManager =
				(WifiManager) context.getSystemService(Context.WIFI_SERVICE);
		WifiInfo wifiInfo = wifiManager.getConnectionInfo();
		if (wifiInfo != null) {
			return wifiInfo.getSSID();
		}
		return null;
	}
	
	public String getWifiIpAddress(){
		WifiManager wifiManager =
				(WifiManager) context.getSystemService(Context.WIFI_SERVICE);
		WifiInfo wifiInfo = wifiManager.getConnectionInfo();
		if (wifiInfo != null) {
			int ip= wifiInfo.getIpAddress();
			if (ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN)) {
			     ip = Integer.reverseBytes(ip);
			}
			byte[] bytes = BigInteger.valueOf(ip).toByteArray();
			String address;
			try {
				address = InetAddress.getByAddress(bytes).getHostAddress();
				return address;
			} catch (UnknownHostException e) {
				e.printStackTrace();
			}
			
		}
		return null;
	}
	
	public HashMap<String, Integer> getAllCellInfoSignalStrength(){
		if(!(ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION)==PackageManager.PERMISSION_GRANTED)){
			return null;
		}
		TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
		List<CellInfo> cellInfos = (List<CellInfo>) telephonyManager.getAllCellInfo();
		
		HashMap<String,	 Integer> results = new HashMap<String, Integer>();
		
		if(cellInfos==null){
			return results;
		}
		
		for(CellInfo cellInfo : cellInfos){
			if(cellInfo.getClass().equals(CellInfoLte.class)){
				results.put("LTE", ((CellInfoLte) cellInfo).getCellSignalStrength().getDbm());
			}else if(cellInfo.getClass().equals(CellInfoGsm.class)){
				results.put("GSM", ((CellInfoGsm) cellInfo).getCellSignalStrength().getDbm());
			}else if(cellInfo.getClass().equals(CellInfoCdma.class)){
				results.put("CDMA", ((CellInfoCdma) cellInfo).getCellSignalStrength().getDbm());
			}else if(cellInfo.getClass().equals(CellInfoWcdma.class)){
              results.put("WCDMA", ((CellInfoWcdma) cellInfo).getCellSignalStrength().getDbm());
			}
		}
		
		return results;
	}
	
	
	
	
	public int getWifiRSSI(){
		WifiManager wifiManager =
				(WifiManager) context.getSystemService(Context.WIFI_SERVICE);
		WifiInfo wifiInfo = wifiManager.getConnectionInfo();
		if (wifiInfo != null) {
			return wifiInfo.getRssi();
		}
		return -1;
	}

	public void switchNetwork(boolean toWiFi, CountDownLatch latch){
		ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
		NetworkRequest.Builder request = new NetworkRequest.Builder();

		if(toWiFi){
			request.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
		}else{
			request.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
		}
		request.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
////		request.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
//		ConnectivityManager.NetworkCallback connectivityNetworkCallback = new ConnectivityNetworkCallback(latch, cm);
		connectivityNetworkCallback = new ConnectivityNetworkCallback(latch, cm);
		cm.requestNetwork(request.build(), connectivityNetworkCallback);

	}

	public void unregisterNetworkCallback(){
		if (connectivityNetworkCallback!=null){
			ConnectivityManager cm=((ConnectivityNetworkCallback)connectivityNetworkCallback).getConnectivityManager();
			cm.bindProcessToNetwork(null);
			cm.unregisterNetworkCallback(connectivityNetworkCallback);
			connectivityNetworkCallback = null;
		}
	}

	class ConnectivityNetworkCallback extends ConnectivityManager.NetworkCallback{
		private CountDownLatch latch;
		private  ConnectivityManager cm;
		public ConnectivityNetworkCallback(CountDownLatch l, ConnectivityManager cm){
			this.latch=l;
			this.cm=cm;
		}

		public ConnectivityManager getConnectivityManager(){
			return this.cm;
		}
		@Override
		public void onAvailable(Network network) {
			super.onAvailable(network);
			cm.setProcessDefaultNetwork(network);
//			cm.bindProcessToNetwork(network);
			this.latch.countDown();
//			this.cm.unregisterNetworkCallback(this);
		}
	}
	

	private synchronized void updateBatteryStat(Intent powerIntent) {
		int scale = powerIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 
				Config.DEFAULT_BATTERY_SCALE);
		int level = powerIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, 
				Config.DEFAULT_BATTERY_LEVEL);
		// change to the unit of percentage
		this.curBatteryLevel = level * 100 / scale;
		this.isCharging = powerIntent.getIntExtra(BatteryManager.EXTRA_STATUS, 
				BatteryManager.BATTERY_STATUS_UNKNOWN) == BatteryManager.BATTERY_STATUS_CHARGING;

		Logger.i(
				"Current power level is " + curBatteryLevel + " and isCharging = " + isCharging);
	}

	private class PowerStateChangeReceiver extends BroadcastReceiver {
		/** 
		 * @see android.content.BroadcastReceiver#onReceive(android.content.Context, 
		 * android.content.Intent)
		 */
		@Override
		public void onReceive(Context context, Intent intent) {
			updateBatteryStat(intent);
		}
	}

	private class SignalStrengthChangeListener extends PhoneStateListener {
		@Override
		public void onSignalStrengthsChanged(SignalStrength signalStrength) {
			String rssis="";

			rssis+="CDMA:"+signalStrength.getCdmaDbm()+"|";
			rssis+="EVDO:"+signalStrength.getEvdoDbm()+"|";
			rssis+="GSM:"+((2*signalStrength.getGsmSignalStrength())-113)+"|";
			try {
				Method[] methods = android.telephony.SignalStrength.class
						.getMethods();
				for (Method mthd : methods) {
					if (mthd.getName().equals("getLteDbm")){
						rssis+="LTE:"+(Integer) mthd.invoke(signalStrength)+"|";
					}

				}
			} catch (SecurityException e) {
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			}
			
			setCurrentRssi(rssis);
			
		}
	}

	//  /**
	//   * Fetches the new connectivity state from the connectivity manager directly.
	//   */
	//  private synchronized void updateConnectivityInfo() {
	//    ConnectivityManager cm = (ConnectivityManager) context
	//            .getSystemService(Context.CONNECTIVITY_SERVICE);
	//    NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
	//    if (activeNetwork != null) {
	//      if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) {
	//        PhoneUtils.this.currentNetworkConnection = TYPE_WIFI;
	//        Logger.i("currentNetworkConnection: TYPE_WIFI");
	//      }
	//      if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) {
	//        PhoneUtils.this.currentNetworkConnection = TYPE_MOBILE;
	//        Logger.i("currentNetworkConnection: TYPE_MOBILE");
	//      }
	//    } else {
	//      PhoneUtils.this.currentNetworkConnection = TYPE_NOT_CONNECTED;
	//      Logger.i("currentNetworkConnection: TYPE_NOT_CONNECTED");
	//    }
	//  }
	//  
	//TODO
	//  /**
	//   * When alerted that the network connectivity has changed, change the 
	//   * stored connectivity value.
	//   */
	//  private class ConnectivityChangeReceiver extends BroadcastReceiver {
	//    
	////    public ConnectivityChangeReceiver() {
	////      super();
	////    }
	//
	//    @Override
	//    public void onReceive(Context context, Intent intent) {
	//      updateConnectivityInfo();
	//
	//    }
	//  }
	//
	//  public synchronized int getCurrentNetworkConnection() {
	//    return currentNetworkConnection;
	//  }

	private String getVersionStr() {
		return String.format("INCREMENTAL:%s, RELEASE:%s, SDK_INT:%s",
				Build.VERSION.INCREMENTAL, Build.VERSION.RELEASE, Build.VERSION.SDK_INT);
	}

	private String getDeviceId() {
		String deviceId=null;
		if(ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE)==PackageManager.PERMISSION_GRANTED){
			// This ID is permanent to a physical phone.
			deviceId = telephonyManager.getDeviceId();
		}



		// "generic" means the emulator.
		if (deviceId == null || Build.DEVICE.equals("generic")) {

			// This ID changes on OS reinstall/factory reset.
			deviceId = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID);
		}

		return deviceId;
	}


	public DeviceInfo getDeviceInfo() {

		if (deviceInfo == null) {
			deviceInfo = new DeviceInfo();
			deviceInfo.deviceId = getDeviceId();
			deviceInfo.manufacturer = Build.MANUFACTURER;
			deviceInfo.model = Build.MODEL;
			deviceInfo.os = getVersionStr();
			deviceInfo.user = Build.VERSION.CODENAME;
		}

		return deviceInfo;
	}

	private Location getMockLocation() {
		return new Location("MockProvider");
	}

	// Hongyi: it is not a good idea to hard code here. Instead we can move those 
	// strings from string.xml to Config.java
	public String getServerUrl() {
		return Config.SERVER_URL;
	}

	public String getAnonymousServerUrl() {
		return Config.ANONYMOUS_SERVER_URL;
	}

	public String getTestingServerUrl() {
		return Config.TEST_SERVER_URL;
	}

	public boolean isTestingServer(String serverUrl) {
		return serverUrl == getTestingServerUrl();
	}

	/**
	 * Using MLab service to detect ipv4 or ipv6 compatibility
	 * @param ip_detect_type -- "ipv4" or "ipv6"
	 * @return IP_TYPE_CANNOT_DECIDE, IP_TYPE_UNCONNECTIVITY, IP_TYPE_CONNECTIVITY
	 */
	private int checkIPCompatibility(String ip_detect_type) {
		if (!ip_detect_type.equals("ipv4") && !ip_detect_type.equals("ipv6")) {
			return IP_TYPE_CANNOT_DECIDE;
		}
		Socket tcpSocket = new Socket();
		try {
			ArrayList<String> hostnameList = MLabNS.Lookup(context, "mobiperf", 
					ip_detect_type, "ip");
			// MLabNS returns at least one ip address
			if (hostnameList.isEmpty())
				return IP_TYPE_CANNOT_DECIDE;
			// Use the first result in the element
			String hostname = hostnameList.get(0);
			SocketAddress remoteAddr = new InetSocketAddress(hostname, portNum);
			tcpSocket.setTcpNoDelay(true);
			tcpSocket.connect(remoteAddr, tcpTimeout);
		} catch (ConnectException e) {
			// Server is not reachable due to client not support ipv6
			Logger.e("Connection exception is " + e.getMessage());
			return IP_TYPE_UNCONNECTIVITY;
		} catch (IOException e) {
			// Client timer expired
			Logger.e("Fail to setup TCP in checkIPCompatibility(). "
					+ e.getMessage());
			return IP_TYPE_CANNOT_DECIDE;
		} catch (InvalidParameterException e) {
			// MLabNS service lookup fail
			Logger.e("InvalidParameterException in checkIPCompatibility(). "
					+ e.getMessage());
			return IP_TYPE_CANNOT_DECIDE;
		} catch (IllegalArgumentException e) {
			Logger.e("IllegalArgumentException in checkIPCompatibility(). "
					+ e.getMessage());
			return IP_TYPE_CANNOT_DECIDE;
		} finally {
			try {
				tcpSocket.close();
			} catch (IOException e) {
				Logger.e("Fail to close TCP in checkIPCompatibility().");
				return IP_TYPE_CANNOT_DECIDE;
			}
		}
		return IP_TYPE_CONNECTIVITY;
	}

	/**
	 * Use MLabNS slices to check IPv4/IPv6 domain name resolvable
	 * @param ip_detect_type -- "ipv4" or "ipv6"
	 * @return DN_UNRESOLVABLE, DN_RESOLVABLE
	 */
	private int checkDomainNameResolvable(String ip_detect_type) {
		if (!ip_detect_type.equals("ipv4") && !ip_detect_type.equals("ipv6")) {
			return DN_UNKNOWN;
		}
		try {
			ArrayList<String> ipAddressList = MLabNS.Lookup(context, "mobiperf", 
					ip_detect_type, "fqdn");
			String ipAddress;
			// MLabNS returns one fqdn each time
			if (ipAddressList.size() == 1) {
				ipAddress = ipAddressList.get(0);
			} else {
				return DN_UNKNOWN;
			}
			InetAddress inet = InetAddress.getByName(ipAddress);
			if (inet != null)
				return DN_RESOLVABLE;
		} catch (UnknownHostException e) {
			// Fail to resolve domain name
			Logger.e("UnknownHostException in checkDomainNameResolvable() "
					+ e.getMessage());
			return DN_UNRESOLVABLE;
		} catch (InvalidParameterException e) {
			// MLabNS service lookup fail
			Logger.e("InvalidParameterException in checkIPCompatibility(). "
					+ e.getMessage());
			return DN_UNRESOLVABLE;
		} catch ( Exception e ) {
			// "catch-all"
			Logger.e("Unexpected Exception: " + e.getMessage());
			return DN_UNRESOLVABLE;
		}
		return DN_UNKNOWN;
	}

	/** 
	 * Summarize ip connectable cases 
	 * @return ipv4, ipv6, ipv4_ipv6, IP_TYPE_NONE or IP_TYPE_UNKNOWN
	 */
	public String getIpConnectivity() {
		int v4Conn = checkIPCompatibility("ipv4");
		int v6Conn = checkIPCompatibility("ipv6");
		if (v4Conn == IP_TYPE_CONNECTIVITY && v6Conn == IP_TYPE_CONNECTIVITY)
			return IP_TYPE_IPV4_IPV6_BOTH;
		if (v4Conn == IP_TYPE_CONNECTIVITY && v6Conn != IP_TYPE_CONNECTIVITY)
			return IP_TYPE_IPV4_ONLY;
		if (v4Conn != IP_TYPE_CONNECTIVITY && v6Conn == IP_TYPE_CONNECTIVITY)
			return IP_TYPE_IPV6_ONLY;
		if (v4Conn == IP_TYPE_UNCONNECTIVITY && v6Conn == IP_TYPE_UNCONNECTIVITY)
			return IP_TYPE_NONE;
		return IP_TYPE_UNKNOWN;
	}

	/**
	 * Summarize Domain Name resolvability cases
	 * @return ipv4, ipv6, ipv4_ipv6, IP_TYPE_NONE or IP_TYPE_UNKNOWN
	 */
	public String getDnResolvability() {
		int v4Resv = checkDomainNameResolvable("ipv4");
		int v6Resv = checkDomainNameResolvable("ipv6");
		if (v4Resv == DN_RESOLVABLE && v6Resv == DN_RESOLVABLE)
			return IP_TYPE_IPV4_IPV6_BOTH;
		if (v4Resv == DN_RESOLVABLE && v6Resv != DN_RESOLVABLE)
			return IP_TYPE_IPV4_ONLY;
		if (v4Resv != DN_RESOLVABLE && v6Resv == DN_RESOLVABLE)
			return IP_TYPE_IPV6_ONLY;
		if (v4Resv == DN_UNRESOLVABLE && v6Resv == DN_UNRESOLVABLE)
			return IP_TYPE_NONE;
		return IP_TYPE_UNKNOWN;
	}

	/** Returns the DeviceProperty needed to report the measurement result */
	public DeviceProperty getDeviceProperty(String requestApp) {
		String carrierName = telephonyManager.getNetworkOperatorName();
		Location location;
		if (isTestingServer(getServerUrl())) {
			location = getMockLocation();
		} else {
			location = getLocation();
		}
		//TODO Test on Veriozn and Sprint, as result may be unreliable on CDMA
		// networks (use getPhoneType() to determine if on a CDMA network)
		String networkCountryIso = telephonyManager.getNetworkCountryIso();

//		NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
		String networkType = PhoneUtils.getPhoneUtils().getNetwork();
		String ipConnectivity = "NOT SUPPORTED";
		String dnResolvability = "NOT SUPPORTED";
		Logger.w("IP connectivity is " + ipConnectivity);
		Logger.w("DN resolvability is " + dnResolvability);
//		if (activeNetwork != null) {
//			networkType = activeNetwork.getTypeName();
//		}
		String versionName = PhoneUtils.getPhoneUtils().getAppVersionName();
		PhoneUtils utils = PhoneUtils.getPhoneUtils();

		Logger.e("Request App is " + requestApp);
		Logger.e("Host apps:");
		for ( String app : PhoneUtils.clientKeySet ) {
			Logger.e(app);
		}
		String mobilyzerVersion = context.getString(R.string.scheduler_version_name);
		Logger.i("Scheduler version = " + mobilyzerVersion);
		return new DeviceProperty(getDeviceInfo().deviceId, versionName,
				System.currentTimeMillis() * 1000, getVersionStr(), ipConnectivity,
				dnResolvability, location.getLongitude(), location.getLatitude(), 
				location.getProvider(), networkType, carrierName, networkCountryIso,
				utils.getCurrentBatteryLevel(), utils.isCharging(), 
				utils.getCellInfo(false), getCellRssi(), getWifiRSSI(), getWifiSSID(), getWifiBSSID(), getWifiIpAddress(),
				mobilyzerVersion, PhoneUtils.clientKeySet, requestApp);
	}  
}