package com.mixpanel.android.util;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import com.mixpanel.android.mpmetrics.MPConfig;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
import java.util.Map;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
/**
* An HTTP utility class for internal use in the Mixpanel library. Not thread-safe.
*/
public class HttpService implements RemoteService {
private static boolean sIsMixpanelBlocked;
private static final int MIN_UNAVAILABLE_HTTP_RESPONSE_CODE = HttpURLConnection.HTTP_INTERNAL_ERROR;
private static final int MAX_UNAVAILABLE_HTTP_RESPONSE_CODE = 599;
@Override
public void checkIsMixpanelBlocked() {
Thread t = new Thread(new Runnable() {
public void run() {
try {
InetAddress apiMixpanelInet = InetAddress.getByName("api.mixpanel.com");
InetAddress decideMixpanelInet = InetAddress.getByName("decide.mixpanel.com");
sIsMixpanelBlocked = apiMixpanelInet.isLoopbackAddress() ||
apiMixpanelInet.isAnyLocalAddress() ||
decideMixpanelInet.isLoopbackAddress() ||
decideMixpanelInet.isAnyLocalAddress();
if (sIsMixpanelBlocked) {
MPLog.v(LOGTAG, "AdBlocker is enabled. Won't be able to use Mixpanel services.");
}
} catch (Exception e) {
}
}
});
t.start();
}
@Override
public boolean isOnline(Context context, OfflineMode offlineMode) {
if (sIsMixpanelBlocked) return false;
if (onOfflineMode(offlineMode)) return false;
boolean isOnline;
try {
final ConnectivityManager cm =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkInfo netInfo = cm.getActiveNetworkInfo();
if (netInfo == null) {
isOnline = true;
MPLog.v(LOGTAG, "A default network has not been set so we cannot be certain whether we are offline");
} else {
isOnline = netInfo.isConnectedOrConnecting();
MPLog.v(LOGTAG, "ConnectivityManager says we " + (isOnline ? "are" : "are not") + " online");
}
} catch (final SecurityException e) {
isOnline = true;
MPLog.v(LOGTAG, "Don't have permission to check connectivity, will assume we are online");
}
return isOnline;
}
private boolean onOfflineMode(OfflineMode offlineMode) {
boolean onOfflineMode;
try {
onOfflineMode = offlineMode != null && offlineMode.isOffline();
} catch (Exception e) {
onOfflineMode = false;
MPLog.v(LOGTAG, "Client State should not throw exception, will assume is not on offline mode", e);
}
return onOfflineMode;
}
@Override
public byte[] performRequest(String endpointUrl, Map<String, Object> params, SSLSocketFactory socketFactory) throws ServiceUnavailableException, IOException {
MPLog.v(LOGTAG, "Attempting request to " + endpointUrl);
byte[] response = null;
// the while(retries) loop is a workaround for a bug in some Android HttpURLConnection
// libraries- The underlying library will attempt to reuse stale connections,
// meaning the second (or every other) attempt to connect fails with an EOFException.
// Apparently this nasty retry logic is the current state of the workaround art.
int retries = 0;
boolean succeeded = false;
while (retries < 3 && !succeeded) {
InputStream in = null;
OutputStream out = null;
BufferedOutputStream bout = null;
HttpURLConnection connection = null;
try {
final URL url = new URL(endpointUrl);
connection = (HttpURLConnection) url.openConnection();
if (null != socketFactory && connection instanceof HttpsURLConnection) {
((HttpsURLConnection) connection).setSSLSocketFactory(socketFactory);
}
connection.setConnectTimeout(2000);
connection.setReadTimeout(10000);
if (null != params) {
Uri.Builder builder = new Uri.Builder();
for (Map.Entry<String, Object> param : params.entrySet()) {
builder.appendQueryParameter(param.getKey(), param.getValue().toString());
}
String query = builder.build().getEncodedQuery();
connection.setFixedLengthStreamingMode(query.getBytes().length);
connection.setDoOutput(true);
connection.setRequestMethod("POST");
out = connection.getOutputStream();
bout = new BufferedOutputStream(out);
bout.write(query.getBytes("UTF-8"));
bout.flush();
bout.close();
bout = null;
out.close();
out = null;
}
in = connection.getInputStream();
response = slurp(in);
in.close();
in = null;
succeeded = true;
} catch (final EOFException e) {
MPLog.d(LOGTAG, "Failure to connect, likely caused by a known issue with Android lib. Retrying.");
retries = retries + 1;
} catch (final IOException e) {
if (connection.getResponseCode() >= MIN_UNAVAILABLE_HTTP_RESPONSE_CODE && connection.getResponseCode() <= MAX_UNAVAILABLE_HTTP_RESPONSE_CODE) {
throw new ServiceUnavailableException("Service Unavailable", connection.getHeaderField("Retry-After"));
} else {
throw e;
}
}
finally {
if (null != bout)
try { bout.close(); } catch (final IOException e) { ; }
if (null != out)
try { out.close(); } catch (final IOException e) { ; }
if (null != in)
try { in.close(); } catch (final IOException e) { ; }
if (null != connection)
connection.disconnect();
}
}
if (retries >= 3) {
MPLog.v(LOGTAG, "Could not connect to Mixpanel service after three retries.");
}
return response;
}
private static byte[] slurp(final InputStream inputStream)
throws IOException {
final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[8192];
while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
return buffer.toByteArray();
}
private static final String LOGTAG = "MixpanelAPI.Message";
}