com.mobilyzer.util.PhoneUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.mobilyzer.util.PhoneUtils.java

Source

/* 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);
    }
}