/* 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.mobiperf.util;
import com.mobiperf.DeviceInfo;
import com.mobiperf.DeviceProperty;
import com.mobiperf.Logger;
import com.mobiperf.R;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Picture;
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.NetworkInfo;
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.telephony.NeighboringCellInfo;
import android.telephony.PhoneStateListener;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
import android.view.Display;
import android.view.WindowManager;
import android.webkit.WebView;
import java.io.IOException;
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.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
/**
* Phone related utilities.
*/
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";
/**
* 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 powerBroadcastReceiver;
private BroadcastReceiver networkBroadcastReceiver;
private int currentSignalStrength = NeighboringCellInfo.UNKNOWN_RSSI;
/** 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;
protected PhoneUtils(Context context) {
this.context = context;
powerBroadcastReceiver = new PowerStateChangeReceiver();
// Registers a receiver for battery change events.
Intent powerIntent = globalContext.registerReceiver(powerBroadcastReceiver,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
updateBatteryStat(powerIntent);
networkBroadcastReceiver = new ConnectivityChangeReceiver();
// Registers a receiver for network change events.
globalContext.registerReceiver(networkBroadcastReceiver,
new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE"));
updateConnectivityInfo(); // does not require the intent to determine connectivity
}
/**
* 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) {
return NETWORK_WIFI;
} else {
return getTelephonyNetworkType();
}
}
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
};
/** Returns mobile data network connection type. */
private String getTelephonyNetworkType() {
assert NETWORK_TYPES[14].compareTo("EHRPD") == 0;
int networkType = telephonyManager.getNetworkType();
if (networkType < NETWORK_TYPES.length) {
// Network type might get changed if getNetwokrType() called twice
return NETWORK_TYPES[networkType];
} 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) {
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() {
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(powerBroadcastReceiver);
context.unregisterReceiver(networkBroadcastReceiver);
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();
}
/**
* Captures a screenshot of a WebView, except scrollbars, and returns it as a
* Bitmap.
*
* @param webView The WebView to screenshot.
* @return A Bitmap with the screenshot.
*/
public static Bitmap captureScreenshot(WebView webView) {
Picture picture = webView.capturePicture();
int width = Math.min(picture.getWidth(),
webView.getWidth() - webView.getVerticalScrollbarWidth());
int height = Math.min(picture.getHeight(), webView.getHeight());
Bitmap bitmap = Bitmap.createBitmap(width, height, Config.RGB_565);
Canvas cv = new Canvas(bitmap);
cv.drawPicture(picture);
return bitmap;
}
/**
* 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;
} 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(int rssi) {
currentSignalStrength = rssi;
}
/**
* Returns the last updated RSSI value
*/
public synchronized int getCurrentRssi() {
initNetwork();
return currentSignalStrength;
}
private synchronized void updateBatteryStat(Intent powerIntent) {
int scale = powerIntent.getIntExtra(BatteryManager.EXTRA_SCALE,
com.mobiperf.Config.DEFAULT_BATTERY_SCALE);
int level = powerIntent.getIntExtra(BatteryManager.EXTRA_LEVEL,
com.mobiperf.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 network = getNetwork();
if (network.equals(NETWORK_TYPES[TelephonyManager.NETWORK_TYPE_CDMA])) {
setCurrentRssi(signalStrength.getCdmaDbm());
} else if (network.equals(NETWORK_TYPES[TelephonyManager.NETWORK_TYPE_EVDO_0]) ||
network.equals(NETWORK_TYPES[TelephonyManager.NETWORK_TYPE_EVDO_A]) ||
network.equals(NETWORK_TYPES[TelephonyManager.NETWORK_TYPE_EVDO_B])) {
setCurrentRssi(signalStrength.getEvdoDbm());
} else if (signalStrength.isGsm()) {
setCurrentRssi(signalStrength.getGsmSignalStrength());
}
}
}
/**
* 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;
}
if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) {
PhoneUtils.this.currentNetworkConnection = TYPE_MOBILE;
}
} else {
PhoneUtils.this.currentNetworkConnection = TYPE_NOT_CONNECTED;
}
}
/**
* When alerted that the network connectivity has changed, change the
* stored connectivity value.
*/
private class ConnectivityChangeReceiver extends BroadcastReceiver {
@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 = telephonyManager.getDeviceId(); // This ID is permanent to a physical phone.
// "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");
}
public String getServerUrl() {
return context.getResources().getString(R.string.serverUrl);
}
public String getAnonymousServerUrl() {
return context.getResources().getString(R.string.anonymousServerUrl);
}
public String getTestingServerUrl() {
return context.getResources().getString(R.string.testServerUrl);
}
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) {
// Fail to resolve domain name
Logger.e("InvalidParameterException in checkIPCompatibility(). "
+ 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 carrierName = telephonyManager.getNetworkOperatorName();
Location location;
if (isTestingServer(getServerUrl())) {
location = getMockLocation();
} else {
location = getLocation();
}
NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
String networkType = PhoneUtils.getPhoneUtils().getNetwork();
String ipConnectivity = getIpConnectivity();
String dnResolvability = getDnResolvability();
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();
return new DeviceProperty(getDeviceInfo().deviceId, versionName,
System.currentTimeMillis() * 1000, getVersionStr(), ipConnectivity,
dnResolvability, location.getLongitude(), location.getLatitude(),
location.getProvider(), networkType, carrierName,
utils.getCurrentBatteryLevel(), utils.isCharging(),
utils.getCellInfo(false), utils.getCurrentRssi());
}
}