diff --git a/.travis.yml b/.travis.yml
index 52c7ec63..0ac44119 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,8 +4,8 @@ jdk:
android:
components:
- tools
- - build-tools-27.0.2
- - android-27
+ - build-tools-28.0.3
+ - android-29
- extra-android-m2repository
env:
diff --git a/app/build.gradle b/app/build.gradle
index 240904ce..f8375ead 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -15,14 +15,14 @@ sonarqube {
}
android {
- compileSdkVersion 27
- buildToolsVersion '27.0.2'
+ compileSdkVersion 29
+ buildToolsVersion '28.0.3'
defaultConfig {
minSdkVersion 14
- targetSdkVersion 27
- versionCode 55
- versionName "2.2.8"
+ targetSdkVersion 29
+ versionCode 56
+ versionName "2.2.10"
applicationId "com.aaronjwood.portauthority"
setProperty("archivesBaseName", "PortAuthority-$versionName")
}
@@ -65,12 +65,11 @@ android {
}
dependencies {
- compile 'com.android.support:support-v4:27.0.2'
- compile 'com.android.support:appcompat-v7:27.0.2'
- compile 'com.squareup.okhttp3:okhttp:3.9.1'
+ compile 'com.android.support:support-v4:28.0.0'
+ compile 'com.android.support:appcompat-v7:28.0.0'
+ compile 'com.squareup.okhttp3:okhttp:3.10.0'
compile 'jcifs:jcifs:1.3.17'
compile 'dnsjava:dnsjava:2.1.7'
- //This does absolutely nothing
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4'
testCompile 'junit:junit:4.12'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6f79851c..4b1238f9 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -5,6 +5,7 @@
+
adapter;
protected ListView portList;
- protected final List ports = Collections.synchronizedList(new ArrayList());
+ protected final List ports = Collections.synchronizedList(new ArrayList<>());
protected ProgressDialog scanProgressDialog;
protected Dialog portRangeDialog;
protected Handler handler;
diff --git a/app/src/main/java/com/aaronjwood/portauthority/activity/MainActivity.java b/app/src/main/java/com/aaronjwood/portauthority/activity/MainActivity.java
index 5283ebc1..c61915c0 100644
--- a/app/src/main/java/com/aaronjwood/portauthority/activity/MainActivity.java
+++ b/app/src/main/java/com/aaronjwood/portauthority/activity/MainActivity.java
@@ -1,5 +1,7 @@
package com.aaronjwood.portauthority.activity;
+import android.Manifest;
+import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
@@ -9,13 +11,17 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.sqlite.SQLiteException;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.AppCompatActivity;
@@ -62,6 +68,7 @@
public final class MainActivity extends AppCompatActivity implements MainAsyncResponse {
private final static int TIMER_INTERVAL = 1500;
+ private final static int COARSE_LOCATION_REQUEST = 1;
private Wireless wifi;
private ListView hostList;
@@ -78,7 +85,7 @@ public final class MainActivity extends AppCompatActivity implements MainAsyncRe
private Handler scanHandler;
private IntentFilter intentFilter = new IntentFilter();
private HostAdapter hostAdapter;
- private List hosts = Collections.synchronizedList(new ArrayList());
+ private List hosts = Collections.synchronizedList(new ArrayList<>());
private Database db;
private DownloadAsyncTask ouiTask;
private DownloadAsyncTask portTask;
@@ -123,16 +130,40 @@ protected void onCreate(Bundle savedInstanceState) {
discoverHostsBtn = findViewById(R.id.discoverHosts);
discoverHostsStr = getResources().getString(R.string.hostDiscovery);
- wifi = new Wireless(getApplicationContext());
+ Context context = getApplicationContext();
+ wifi = new Wireless(context);
scanHandler = new Handler(Looper.getMainLooper());
checkDatabase();
- db = Database.getInstance(getApplicationContext());
+ db = Database.getInstance(context);
setupHostsAdapter();
setupDrawer();
setupHostDiscovery();
+ // Android 8+ now require this permission to read the SSID.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ if (!UserPreference.getLocationPermDiag(context)) {
+ Activity activity = this;
+ new AlertDialog.Builder(activity, R.style.DialogTheme).setTitle("SSID Access")
+ .setMessage("Android 8+ now requires location permissions to read the SSID. " +
+ "If this is not something you're comfortable with just deny the request and go without the functionality.")
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ dialogInterface.dismiss();
+ UserPreference.saveLocationPermDiag(context);
+ if (ContextCompat.checkSelfPermission(context,
+ Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
+ COARSE_LOCATION_REQUEST);
+ }
+ }
+ })
+ .setIcon(android.R.drawable.ic_dialog_alert).show().setCanceledOnTouchOutside(false);
+ }
+ }
+
intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
}
@@ -455,7 +486,7 @@ public void run() {
Errors.showError(context, resources.getString(R.string.failedSignal));
return;
}
-
+
signalStrength.setText(String.format(resources.getString(R.string.signalLink), signal, speed));
signalHandler.postDelayed(this, TIMER_INTERVAL);
}
diff --git a/app/src/main/java/com/aaronjwood/portauthority/async/ScanHostsAsyncTask.java b/app/src/main/java/com/aaronjwood/portauthority/async/ScanHostsAsyncTask.java
index f648a11b..a348e65b 100644
--- a/app/src/main/java/com/aaronjwood/portauthority/async/ScanHostsAsyncTask.java
+++ b/app/src/main/java/com/aaronjwood/portauthority/async/ScanHostsAsyncTask.java
@@ -1,6 +1,8 @@
package com.aaronjwood.portauthority.async;
import android.os.AsyncTask;
+import android.os.Build;
+import android.util.Pair;
import com.aaronjwood.portauthority.db.Database;
import com.aaronjwood.portauthority.network.Host;
@@ -16,6 +18,8 @@
import java.lang.ref.WeakReference;
import java.net.InetAddress;
import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@@ -27,6 +31,9 @@ public class ScanHostsAsyncTask extends AsyncTask {
private final WeakReference delegate;
private Database db;
private static final String ARP_TABLE = "/proc/net/arp";
+ private static final String IP_CMD = "ip neighbor";
+ private static final String NEIGHBOR_INCOMPLETE = "INCOMPLETE";
+ private static final String NEIGHBOR_FAILED = "FAILED";
private static final String ARP_INCOMPLETE = "0x0";
private static final String ARP_INACTIVE = "00:00:00:00:00:00";
private static final int NETBIOS_FILE_SERVER = 0x20;
@@ -51,14 +58,38 @@ protected Void doInBackground(Integer... params) {
int ipv4 = params[0];
int cidr = params[1];
int timeout = params[2];
-
MainAsyncResponse activity = delegate.get();
- File file = new File(ARP_TABLE);
- if (!file.exists() || !file.canRead()) {
- activity.processFinish(new FileNotFoundException("Unable to access device ARP table"));
- activity.processFinish(true);
- return null;
+ // Android 10+ doesn't let us access the ARP table.
+ // Do an early check to see if we can get what we need from the system.
+ // https://developer.android.com/about/versions/10/privacy/changes#proc-net-filesystem
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ try {
+ Process ipProc = Runtime.getRuntime().exec(IP_CMD);
+ ipProc.waitFor();
+ if (ipProc.exitValue() != 0) {
+ activity.processFinish(new IOException("Unable to access ARP entries"));
+ activity.processFinish(true);
+
+ return null;
+ }
+ } catch (IOException | InterruptedException e) {
+ activity.processFinish(new IOException("Unable to parse ARP entries"));
+ activity.processFinish(true);
+ }
+ } else {
+ File file = new File(ARP_TABLE);
+ if (!file.exists()) {
+ activity.processFinish(new FileNotFoundException("Unable to find ARP table"));
+ activity.processFinish(true);
+
+ return null;
+ }
+
+ if (!file.canRead()) {
+ activity.processFinish(new IOException("Unable to read ARP table"));
+ activity.processFinish(true);
+ }
}
ExecutorService executor = Executors.newCachedThreadPool();
@@ -107,63 +138,103 @@ protected final void onProgressUpdate(Void... params) {
final MainAsyncResponse activity = delegate.get();
ExecutorService executor = Executors.newCachedThreadPool();
final AtomicInteger numHosts = new AtomicInteger(0);
+ List> pairs = new ArrayList<>();
try {
- reader = new BufferedReader(new InputStreamReader(new FileInputStream(ARP_TABLE), "UTF-8"));
- reader.readLine(); // Skip header.
- String line;
-
- while ((line = reader.readLine()) != null) {
- String[] arpLine = line.split("\\s+");
-
- final String ip = arpLine[0];
- final String flag = arpLine[2];
- final String macAddress = arpLine[3];
-
- if (!ARP_INCOMPLETE.equals(flag) && !ARP_INACTIVE.equals(macAddress)) {
- numHosts.incrementAndGet();
- executor.execute(new Runnable() {
-
- @Override
- public void run() {
- Host host;
- try {
- host = new Host(ip, macAddress, db);
- } catch (IOException e) {
- host = new Host(ip, macAddress);
- }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ Process ipProc = Runtime.getRuntime().exec(IP_CMD);
+ ipProc.waitFor();
+ if (ipProc.exitValue() != 0) {
+ throw new Exception("Unable to access ARP entries");
+ }
- MainAsyncResponse activity = delegate.get();
- try {
- InetAddress add = InetAddress.getByName(ip);
- String hostname = add.getCanonicalHostName();
- host.setHostname(hostname);
+ reader = new BufferedReader(new InputStreamReader(ipProc.getInputStream()));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ String[] neighborLine = line.split("\\s+");
- if (activity != null) {
- activity.processFinish(host, numHosts);
- }
- } catch (UnknownHostException e) {
- numHosts.decrementAndGet();
- activity.processFinish(e);
- return;
+ // We don't have a validated ARP entry for this case.
+ if (neighborLine.length <= 4) {
+ continue;
+ }
+
+ String ip = neighborLine[0];
+ InetAddress addr = InetAddress.getByName(ip);
+ if (addr.isLinkLocalAddress() || addr.isLoopbackAddress()) {
+ continue;
+ }
+
+ String macAddress = neighborLine[4];
+ String state = neighborLine[neighborLine.length - 1];
+
+ // Determine if the ARP entry is valid.
+ // https://github.com/sivasankariit/iproute2/blob/master/ip/ipneigh.c
+ if (!NEIGHBOR_FAILED.equals(state) && !NEIGHBOR_INCOMPLETE.equals(state)) {
+ pairs.add(new Pair<>(ip, macAddress));
+ }
+ }
+ } else {
+ reader = new BufferedReader(new InputStreamReader(new FileInputStream(ARP_TABLE), "UTF-8"));
+ reader.readLine(); // Skip header.
+ String line;
+
+ while ((line = reader.readLine()) != null) {
+ String[] arpLine = line.split("\\s+");
+ String ip = arpLine[0];
+ String flag = arpLine[2];
+ String macAddress = arpLine[3];
+
+ if (!ARP_INCOMPLETE.equals(flag) && !ARP_INACTIVE.equals(macAddress)) {
+ pairs.add(new Pair<>(ip, macAddress));
+ }
+ }
+ }
+
+ numHosts.addAndGet(pairs.size());
+ for (Pair pair : pairs) {
+ String ip = pair.first;
+ String macAddress = pair.second;
+ executor.execute(new Runnable() {
+
+ @Override
+ public void run() {
+ Host host;
+ try {
+ host = new Host(ip, macAddress, db);
+ } catch (IOException e) {
+ host = new Host(ip, macAddress);
+ }
+
+ MainAsyncResponse activity = delegate.get();
+ try {
+ InetAddress add = InetAddress.getByName(ip);
+ String hostname = add.getCanonicalHostName();
+ host.setHostname(hostname);
+
+ if (activity != null) {
+ activity.processFinish(host, numHosts);
}
+ } catch (UnknownHostException e) {
+ numHosts.decrementAndGet();
+ activity.processFinish(e);
+ return;
+ }
- try {
- NbtAddress[] netbios = NbtAddress.getAllByAddress(ip);
- for (NbtAddress addr : netbios) {
- if (addr.getNameType() == NETBIOS_FILE_SERVER) {
- host.setHostname(addr.getHostName());
- return;
- }
+ try {
+ NbtAddress[] netbios = NbtAddress.getAllByAddress(ip);
+ for (NbtAddress addr : netbios) {
+ if (addr.getNameType() == NETBIOS_FILE_SERVER) {
+ host.setHostname(addr.getHostName());
+ return;
}
- } catch (UnknownHostException e) {
- // It's common that many discovered hosts won't have a NetBIOS entry.
}
+ } catch (UnknownHostException e) {
+ // It's common that many discovered hosts won't have a NetBIOS entry.
}
- });
- }
+ }
+ });
}
- } catch (IOException e) {
+ } catch (Exception e) {
if (activity != null) {
activity.processFinish(e);
}
diff --git a/app/src/main/java/com/aaronjwood/portauthority/async/WolAsyncTask.java b/app/src/main/java/com/aaronjwood/portauthority/async/WolAsyncTask.java
index 2e5e34de..ad24b639 100644
--- a/app/src/main/java/com/aaronjwood/portauthority/async/WolAsyncTask.java
+++ b/app/src/main/java/com/aaronjwood/portauthority/async/WolAsyncTask.java
@@ -21,7 +21,7 @@ protected Void doInBackground(String... params) {
String ip = params[1];
byte[] macBytes = new byte[6];
- String[] macHex = mac.split("(:|-)");
+ String[] macHex = mac.split("([:\\-])");
for (int i = 0; i < 6; i++) {
macBytes[i] = (byte) Integer.parseInt(macHex[i], 16);
}
diff --git a/app/src/main/java/com/aaronjwood/portauthority/utils/UserPreference.java b/app/src/main/java/com/aaronjwood/portauthority/utils/UserPreference.java
index e9f7a0b8..44dfdfd8 100644
--- a/app/src/main/java/com/aaronjwood/portauthority/utils/UserPreference.java
+++ b/app/src/main/java/com/aaronjwood/portauthority/utils/UserPreference.java
@@ -22,6 +22,7 @@ public class UserPreference {
private static final String LAN_SOCKET_TIMEOUT = "lanTimeout";
private static final String WAN_SOCKET_TIMEOUT = "wanTimeout";
private static final String HOST_SOCKET_TIMEOUT = "hostTimeout";
+ private static final String LOCATION_PERM_DIAG = "locationPermDiag";
/**
* Saves the last used host address for later use.
@@ -37,6 +38,22 @@ public static void saveLastUsedHostAddress(@NonNull Context context, @Nullable S
}
}
+ /**
+ * Saves the state of the location permission dialog.
+ */
+ public static void saveLocationPermDiag(@NonNull Context context) {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ preferences.edit().putBoolean(LOCATION_PERM_DIAG, true).apply();
+ }
+
+ /**
+ * Saves the state of the location permission dialog.
+ */
+ public static boolean getLocationPermDiag(@NonNull Context context) {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ return preferences.getBoolean(LOCATION_PERM_DIAG, false);
+ }
+
/**
* Gets the last used host address or an empty string if there isn't one.
*/
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
new file mode 100644
index 00000000..6f7a6111
--- /dev/null
+++ b/app/src/main/res/values-nl/strings.xml
@@ -0,0 +1,59 @@
+
+
+
+ Port Authority
+ Instellingen
+ MAC
+ Leverancier
+ LAN IP
+ WAN IP
+ Ophalen…
+ Signaal/Snelheid
+ Ontdek hosts
+ SSID
+ BSSID
+ Scan welbekende poorten
+ Scan poortbereik
+ Reset poortbereik
+ Open poorten
+ Beginpoort
+ Eindpoort
+ Wifi is niet ingeschakeld
+ Geen wifiverbinding
+ URL/IP
+ WAARSCHUWING
+ Domeinnaam
+ DNS-lookup
+ Wakker maken
+ Wakker aan het maken %1$s…
+ IP-adres
+ Ophalen van DNS-records…
+ Voer een domein in en selecteer een recordtype
+ Je bent niet verbonden met een lokaal netwerk
+ Je bent niet verbonden met een wifinetwerk!
+ %1$ddBm/%2$dMbps
+ Scannen naar hosts
+ %1$d hosts in je subnet
+ Sorteer
+ Op hostnaam
+ Op leverancier
+ Kopieer hostnaam
+ Kopieer IP
+ Kopieer MAC
+ Opvragen MAC-leverancier is mislukt
+ Openen database is mislukt
+ Gegevens aan het downloaden
+ Opgraven van aantal hosts in dit subnet is mislukt
+ Opvragen signaalsterkte is mislukt
+ Opvragen verbindingssnelheid is mislukt
+ Opvragen SSID is mislukt
+ Opvragen BSSID is mislukt
+ Opvragen WifiManager van dit apparaat is mislukt
+ Geen wifi-interface gevonden
+
+ Houd er rekening mee dat het uitvoeren van poortscans tegen openbare servers normaal gesproken als kwaadaardig wordt beschouwd.
+ Bovendien zal het uitvoeren van dit soort scans over het netwerk van je provider in plaats van je eigen wifiverbinding trager verlopen en soms onnauwkeurige resultaten opleveren als gevolg van traffic shaping.
+ Je provider kan ook het verkeer dat door deze scan wordt gegenereerd als kwaadaardig beschouwen.
+
+
+
diff --git a/build.gradle b/build.gradle
index 197b6f29..87039655 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,7 +6,7 @@ buildscript {
google()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.0.1'
+ classpath 'com.android.tools.build:gradle:3.5.3'
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.4'
}
}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index d47e3bac..e52b72fa 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Tue Oct 17 17:47:38 EDT 2017
+#Wed Jan 01 19:13:52 PST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip