|
|
@@ -19,21 +19,28 @@
|
|
|
|
|
|
package ca.psiphon.psibot;
|
|
|
|
|
|
+import android.annotation.TargetApi;
|
|
|
import android.content.Context;
|
|
|
import android.net.ConnectivityManager;
|
|
|
+import android.net.LinkProperties;
|
|
|
import android.net.NetworkInfo;
|
|
|
+import android.os.Build;
|
|
|
|
|
|
import org.apache.http.conn.util.InetAddressUtils;
|
|
|
|
|
|
+import java.io.ByteArrayOutputStream;
|
|
|
import java.io.File;
|
|
|
import java.io.FileOutputStream;
|
|
|
import java.io.IOException;
|
|
|
import java.io.InputStream;
|
|
|
import java.io.OutputStream;
|
|
|
+import java.lang.reflect.InvocationTargetException;
|
|
|
+import java.lang.reflect.Method;
|
|
|
import java.net.InetAddress;
|
|
|
import java.net.NetworkInterface;
|
|
|
import java.net.SocketException;
|
|
|
import java.util.ArrayList;
|
|
|
+import java.util.Collection;
|
|
|
import java.util.Collections;
|
|
|
import java.util.List;
|
|
|
|
|
|
@@ -60,6 +67,17 @@ public class Utils {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ public static void writeRawResourceFile(
|
|
|
+ Context context, int resId, File file, boolean setExecutable) throws IOException {
|
|
|
+ file.delete();
|
|
|
+ Utils.copyStream(
|
|
|
+ context.getResources().openRawResource(resId),
|
|
|
+ new FileOutputStream(file));
|
|
|
+ if (setExecutable && !file.setExecutable(true)) {
|
|
|
+ throw new IOException("failed to set file as executable");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
public static void copyStream(
|
|
|
InputStream inputStream, OutputStream outputStream) throws IOException {
|
|
|
try {
|
|
|
@@ -74,22 +92,19 @@ public class Utils {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- public static void writeRawResourceFile(
|
|
|
- Context context, int resId, File file, boolean setExecutable) throws IOException {
|
|
|
- file.delete();
|
|
|
- // TODO: is this compression redundant?
|
|
|
- /*
|
|
|
- InputStream zippedAsset = context.getResources().openRawResource(resId);
|
|
|
- ZipInputStream zipStream = new ZipInputStream(zippedAsset);
|
|
|
- zipStream.getNextEntry();
|
|
|
- Utils.copyStream(zipStream, new FileOutputStream(file));
|
|
|
- */
|
|
|
- Utils.copyStream(
|
|
|
- context.getResources().openRawResource(resId),
|
|
|
- new FileOutputStream(file));
|
|
|
- if (setExecutable && !file.setExecutable(true)) {
|
|
|
- throw new IOException("failed to set file as executable");
|
|
|
+ public static String readInputStreamToString(InputStream inputStream) throws IOException {
|
|
|
+ return new String(readInputStreamToBytes(inputStream), "UTF-8");
|
|
|
+ }
|
|
|
+
|
|
|
+ public static byte[] readInputStreamToBytes(InputStream inputStream) throws IOException {
|
|
|
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
|
|
+ int readCount;
|
|
|
+ byte[] buffer = new byte[16384];
|
|
|
+ while ((readCount = inputStream.read(buffer, 0, buffer.length)) != -1) {
|
|
|
+ outputStream.write(buffer, 0, readCount);
|
|
|
}
|
|
|
+ outputStream.flush();
|
|
|
+ return outputStream.toByteArray();
|
|
|
}
|
|
|
|
|
|
public static String getNetworkTypeName(Context context) {
|
|
|
@@ -210,4 +225,110 @@ public class Utils {
|
|
|
}
|
|
|
return null;
|
|
|
}
|
|
|
+
|
|
|
+ public static String getFirstActiveNetworkDnsResolver(Context context)
|
|
|
+ throws Utils.PsibotError {
|
|
|
+ Collection<InetAddress> dnsResolvers = Utils.getActiveNetworkDnsResolvers(context);
|
|
|
+ if (!dnsResolvers.isEmpty()) {
|
|
|
+ // strip the leading slash e.g., "/192.168.1.1"
|
|
|
+ String dnsResolver = dnsResolvers.iterator().next().toString();
|
|
|
+ if (dnsResolver.startsWith("/")) {
|
|
|
+ dnsResolver = dnsResolver.substring(1);
|
|
|
+ }
|
|
|
+ return dnsResolver;
|
|
|
+ }
|
|
|
+ throw new Utils.PsibotError("no active network DNS resolver");
|
|
|
+ }
|
|
|
+
|
|
|
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
|
|
+ public static Collection<InetAddress> getActiveNetworkDnsResolvers(Context context)
|
|
|
+ throws Utils.PsibotError {
|
|
|
+ final String errorMessage = "getActiveNetworkDnsResolvers failed";
|
|
|
+ ArrayList<InetAddress> dnsAddresses = new ArrayList<InetAddress>();
|
|
|
+ try {
|
|
|
+ /*
|
|
|
+
|
|
|
+ Hidden API
|
|
|
+ - only available in Android 4.0+
|
|
|
+ - no guarantee will be available beyond 4.2, or on all vendor devices
|
|
|
+
|
|
|
+ core/java/android/net/ConnectivityManager.java:
|
|
|
+
|
|
|
+ /** {@hide} * /
|
|
|
+ public LinkProperties getActiveLinkProperties() {
|
|
|
+ try {
|
|
|
+ return mService.getActiveLinkProperties();
|
|
|
+ } catch (RemoteException e) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ services/java/com/android/server/ConnectivityService.java:
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Return LinkProperties for the active (i.e., connected) default
|
|
|
+ * network interface. It is assumed that at most one default network
|
|
|
+ * is active at a time. If more than one is active, it is indeterminate
|
|
|
+ * which will be returned.
|
|
|
+ * @return the ip properties for the active network, or {@code null} if
|
|
|
+ * none is active
|
|
|
+ * /
|
|
|
+ @Override
|
|
|
+ public LinkProperties getActiveLinkProperties() {
|
|
|
+ return getLinkProperties(mActiveDefaultNetwork);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public LinkProperties getLinkProperties(int networkType) {
|
|
|
+ enforceAccessPermission();
|
|
|
+ if (isNetworkTypeValid(networkType)) {
|
|
|
+ final NetworkStateTracker tracker = mNetTrackers[networkType];
|
|
|
+ if (tracker != null) {
|
|
|
+ return tracker.getLinkProperties();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ core/java/android/net/LinkProperties.java:
|
|
|
+
|
|
|
+ public Collection<InetAddress> getDnses() {
|
|
|
+ return Collections.unmodifiableCollection(mDnses);
|
|
|
+ }
|
|
|
+
|
|
|
+ */
|
|
|
+
|
|
|
+ ConnectivityManager connectivityManager =
|
|
|
+ (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
|
+ Class<?> LinkPropertiesClass = Class.forName("android.net.LinkProperties");
|
|
|
+ Method getActiveLinkPropertiesMethod = ConnectivityManager.class.getMethod("getActiveLinkProperties", new Class []{});
|
|
|
+ Object linkProperties = getActiveLinkPropertiesMethod.invoke(connectivityManager);
|
|
|
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
|
|
+ Method getDnsesMethod = LinkPropertiesClass.getMethod("getDnses", new Class []{});
|
|
|
+ Collection<?> dnses = (Collection<?>)getDnsesMethod.invoke(linkProperties);
|
|
|
+ for (Object dns : dnses) {
|
|
|
+ dnsAddresses.add((InetAddress)dns);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // LinkProperties is public in API 21 (and the DNS function signature has changed)
|
|
|
+ for (InetAddress dns : ((LinkProperties)linkProperties).getDnsServers()) {
|
|
|
+ dnsAddresses.add(dns);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (ClassNotFoundException e) {
|
|
|
+ throw new Utils.PsibotError(errorMessage, e);
|
|
|
+ } catch (NoSuchMethodException e) {
|
|
|
+ throw new Utils.PsibotError(errorMessage, e);
|
|
|
+ } catch (IllegalArgumentException e) {
|
|
|
+ throw new Utils.PsibotError(errorMessage, e);
|
|
|
+ } catch (IllegalAccessException e) {
|
|
|
+ throw new Utils.PsibotError(errorMessage, e);
|
|
|
+ } catch (InvocationTargetException e) {
|
|
|
+ throw new Utils.PsibotError(errorMessage, e);
|
|
|
+ } catch (NullPointerException e) {
|
|
|
+ throw new Utils.PsibotError(errorMessage, e);
|
|
|
+ }
|
|
|
+
|
|
|
+ return dnsAddresses;
|
|
|
+ }
|
|
|
}
|