package com.bugsnag.android;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.BatteryManager;
import android.os.Environment;
import android.os.StatFs;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.io.IOException;
import java.util.Date;
/**
* Information about the current Android device which can change over time,
* including free memory, sensor status and orientation.
* <p/>
* App information in this class is not cached, and is recalculated every
* time toStream is called.
*/
class DeviceState implements JsonStream.Streamable {
private final Long freeMemory;
private final String orientation;
private final Float batteryLevel;
private final Long freeDisk;
private final Boolean charging;
private final String locationStatus;
private final String networkAccess;
private final String time;
DeviceState(@NonNull Context appContext) {
freeMemory = getFreeMemory();
orientation = getOrientation(appContext);
batteryLevel = getBatteryLevel(appContext);
freeDisk = getFreeDisk();
charging = isCharging(appContext);
locationStatus = getLocationStatus(appContext);
networkAccess = getNetworkAccess(appContext);
time = getTime();
}
public void toStream(@NonNull JsonStream writer) throws IOException {
writer.beginObject();
writer.name("freeMemory").value(freeMemory);
writer.name("orientation").value(orientation);
writer.name("batteryLevel").value(batteryLevel);
writer.name("freeDisk").value(freeDisk);
writer.name("charging").value(charging);
writer.name("locationStatus").value(locationStatus);
writer.name("networkAccess").value(networkAccess);
writer.name("time").value(time);
writer.endObject();
}
/**
* Get the amount of memory remaining that the VM can allocate
*/
@NonNull
private static Long getFreeMemory() {
if (Runtime.getRuntime().maxMemory() != Long.MAX_VALUE) {
return Runtime.getRuntime().maxMemory() - Runtime.getRuntime().totalMemory() + Runtime.getRuntime().freeMemory();
} else {
return Runtime.getRuntime().freeMemory();
}
}
/**
* Get the device orientation, eg. "landscape"
*/
@Nullable
private static String getOrientation(Context appContext) {
String orientation = null;
switch (appContext.getResources().getConfiguration().orientation) {
case android.content.res.Configuration.ORIENTATION_LANDSCAPE:
orientation = "landscape";
break;
case android.content.res.Configuration.ORIENTATION_PORTRAIT:
orientation = "portrait";
break;
default:
orientation = null;
break;
}
return orientation;
}
/**
* Get the current battery charge level, eg 0.3
*/
@Nullable
private static Float getBatteryLevel(Context appContext) {
try {
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = appContext.registerReceiver(null, ifilter);
return batteryStatus.getIntExtra("level", -1) / (float) batteryStatus.getIntExtra("scale", -1);
} catch (Exception e) {
Logger.warn("Could not get batteryLevel");
}
return null;
}
/**
* Get the free disk space on the smallest disk
*/
@Nullable
private static Long getFreeDisk() {
try {
StatFs externalStat = new StatFs(Environment.getExternalStorageDirectory().getPath());
long externalBytesAvailable = (long) externalStat.getBlockSize() * (long) externalStat.getBlockCount();
StatFs internalStat = new StatFs(Environment.getDataDirectory().getPath());
long internalBytesAvailable = (long) internalStat.getBlockSize() * (long) internalStat.getBlockCount();
return Math.min(internalBytesAvailable, externalBytesAvailable);
} catch (Exception e) {
Logger.warn("Could not get freeDisk");
}
return null;
}
/**
* Is the device currently charging/full battery?
*/
@Nullable
private static Boolean isCharging(Context appContext) {
try {
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = appContext.registerReceiver(null, ifilter);
int status = batteryStatus.getIntExtra("status", -1);
return (status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL);
} catch (Exception e) {
Logger.warn("Could not get charging status");
}
return null;
}
/**
* Get the current status of location services
*/
@Nullable
private static String getLocationStatus(Context appContext) {
try {
ContentResolver cr = appContext.getContentResolver();
String providersAllowed = Settings.Secure.getString(cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
if (providersAllowed != null && providersAllowed.length() > 0) {
return "allowed";
} else {
return "disallowed";
}
} catch (Exception e) {
Logger.warn("Could not get locationStatus");
}
return null;
}
/**
* Get the current status of network access, eg "cellular"
*/
@Nullable
private static String getNetworkAccess(Context appContext) {
try {
ConnectivityManager cm = (ConnectivityManager) appContext.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
if (activeNetwork != null && activeNetwork.isConnectedOrConnecting()) {
if (activeNetwork.getType() == 1) {
return "wifi";
} else if (activeNetwork.getType() == 9) {
return "ethernet";
} else {
// We default to cellular as the other enums are all cellular in some
// form or another
return "cellular";
}
} else {
return "none";
}
} catch (Exception e) {
Logger.warn("Could not get network access information, we recommend granting the 'android.permission.ACCESS_NETWORK_STATE' permission");
}
return null;
}
/**
* Get the current time on the device, in ISO8601 format.
*/
@NonNull
private String getTime() {
return DateUtils.toISO8601(new Date());
}
}