Skip to content

Commit

Permalink
release: '002.00.50-16API'
Browse files Browse the repository at this point in the history
Add code to properly handle unstable WiFi network conditions.

In older versions of Android, an open/bound ServerSocket
could survive network disconnect followed by subsequent reconnect.

In newer versions of Android, an open/bound ServerSocket
throws a "Socket closed" IOException upon network disconnect.

This added code will detect this condition,
and poll every 15 seconds to determine when the network is available.
At this time, a new ServerSocket is open/bound.
If the IP assigned to the device on the WiFi network is changed,
the foreground service notification is updated.
  • Loading branch information
warren-bank committed Nov 15, 2022
1 parent b96c9a4 commit 38cf3ec
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.apache.http.protocol.ResponseServer;
import org.apache.http.util.EntityUtils;

import android.content.Context;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log;
Expand All @@ -52,18 +53,26 @@
import java.util.concurrent.Executors;

public class RequestListenerThread extends Thread {
public static interface Callback {
public void onNewIpAddress();
}

private static final String tag = RequestListenerThread.class.getSimpleName();

public static Map<String, byte[]> photoCacheMaps = Collections.synchronizedMap(new HashMap<String, byte[]>());
private static Map<String, Socket> socketMaps = Collections.synchronizedMap(new HashMap<String, Socket>());
private static String macAddress = null;

private Context context;
private Callback callback;
private ServerSocket serversocket;
private HttpParams params;
private InetAddress localAddress;
private MyHttpService httpService;

public RequestListenerThread() {
public RequestListenerThread(Context context, Callback callback) {
this.context = context;
this.callback = callback;
}

public void run() {
Expand All @@ -81,7 +90,7 @@ public void run() {

while (!Thread.interrupted()) {
try {
if (serversocket == null)
if (this.serversocket == null)
break;

Socket socket = this.serversocket.accept();
Expand All @@ -94,9 +103,54 @@ public void run() {
thread.setDaemon(true);
exec.execute(thread);
}
catch (IOException e) {
Log.e(tag, "problem accepting inbound HTTP connection", e);
break;
catch(Exception e) {
int wifi_connection_status = get_wifi_connection_status();

if ((e instanceof IOException) && (wifi_connection_status == 0)) {
while (wifi_connection_status == 0) {
// Socket closed due to temporary disconnection from WiFi network.
// Check every 15 seconds for reconnection.
try {
Thread.sleep(15 * 1000);
wifi_connection_status = get_wifi_connection_status();
}
catch (InterruptedException e2) {
break;
}
}
if (wifi_connection_status == 0) {
// Thread was interrupted
break;
}
if ((this.serversocket != null) && !this.serversocket.isClosed()) {
// Close the previous socket
try {
this.serversocket.close();
}
catch (IOException e2) {
this.serversocket = null;
}
}
if ((this.serversocket == null) || this.serversocket.isClosed()) {
// Open a new socket
try {
initHttpServer();

if ((wifi_connection_status == 2) && (this.callback != null)) {
this.callback.onNewIpAddress();
}
}
catch (IOException e2) {
Log.e(tag, "problem reinitializing HTTP server", e);
this.serversocket = null;
}
}
}
else {
// all other errors are fatal to the HTTP server
Log.e(tag, "problem accepting inbound HTTP connection", e);
break;
}
}
}
exec.shutdown();
Expand All @@ -116,22 +170,38 @@ public void destroy() {
}
}

/*
* return values:
* 0 = disconnected
* 1 = connected to same local address
* 2 = connected to new local address
*/
private int get_wifi_connection_status() {
InetAddress currentLocalAddress = NetworkUtils.getLocalIpAddress(this.context);

return (currentLocalAddress == null)
? 0
: currentLocalAddress.equals(this.localAddress)
? 1
: 2;
}

private void initHttpServer() throws IOException {
Log.d(tag, "airplay init http server");

localAddress = NetworkUtils.getLocalIpAddress();
this.localAddress = NetworkUtils.getLocalIpAddress(this.context);

if (localAddress == null) {
if (this.localAddress == null) {
Thread.interrupted();
return;
}

if (macAddress == null) {
macAddress = NetworkUtils.getMACAddress(localAddress)[0];
macAddress = NetworkUtils.getMACAddress(this.localAddress)[0];
Log.d(tag, "airplay local MAC address = " + macAddress);
}

serversocket = new ServerSocket(Constant.AIRPLAY_PORT, 2, localAddress);
serversocket = new ServerSocket(Constant.AIRPLAY_PORT, 2, this.localAddress);
serversocket.setReuseAddress(true);

params = new BasicHttpParams();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
import java.util.HashMap;
import java.util.Locale;

public class NetworkingService extends Service {
public class NetworkingService extends Service implements RequestListenerThread.Callback {
private static final String tag = NetworkingService.class.getSimpleName();
private static final String ACTION_STOP = "STOP";
public static final String ACTION_PLAY = "PLAY";
Expand Down Expand Up @@ -77,7 +77,7 @@ public void onCreate() {
new Thread() {
public void run() {
try {
thread = new RequestListenerThread();
thread = new RequestListenerThread(/* context= */ NetworkingService.this, /* RequestListenerThread.Callback */ NetworkingService.this);
thread.setDaemon(false);
thread.start();

Expand Down Expand Up @@ -181,7 +181,7 @@ private void registerAirplay() throws IOException {
Log.d(tag, "Beginning registration of Bonjour services..");

if (localAddress == null)
localAddress = NetworkUtils.getLocalIpAddress();
localAddress = NetworkUtils.getLocalIpAddress(NetworkingService.this);

if (localAddress == null) {
Log.d(tag, "No local IP address found for any network interface that supports multicast");
Expand Down Expand Up @@ -305,7 +305,7 @@ private PendingIntent getPendingIntent_StopService() {

private String getNetworkAddress() {
if (localAddress == null)
localAddress = NetworkUtils.getLocalIpAddress();
localAddress = NetworkUtils.getLocalIpAddress(NetworkingService.this);

return (localAddress == null)
? "[offline]"
Expand Down Expand Up @@ -381,4 +381,11 @@ public static PlayerManager getPlayerManager() {
return playerManager;
}

// -------------------------------------------------------------------------
// implement interface: RequestListenerThread.Callback

public void onNewIpAddress() {
showNotification();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,37 @@

public class NetworkUtils {

public synchronized static Inet4Address getLocalIpAddress() {
public static boolean isWifiConnected(Context context) {
try {
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

//Get status
NetworkInfo.State wifi = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI).getState();

//Determine the conditions of wifi connection
if (wifi != NetworkInfo.State.CONNECTED)
throw new Exception("not connected");

return true;
}
catch(Exception e) {
return false;
}
}

public static String getLocalIp(Context context) {
int ip = getWifiIpAddress(context);
return (ip > 0)
? formatIpAddress(ip)
: null;
}

public synchronized static Inet4Address getLocalIpAddress(Context context) {
try {
int ip = getWifiIpAddress(context);
byte[] address = convertIpAddress(ip);
if (address == null) throw new Exception("no IP is assigned for WiFi network");

for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
NetworkInterface intf = en.nextElement();

Expand All @@ -28,14 +57,18 @@ public synchronized static Inet4Address getLocalIpAddress() {

if (!inetAddress.isLoopbackAddress()) {
if (inetAddress instanceof Inet4Address) {
return ((Inet4Address) inetAddress);
if (sameIpAddress(address, (Inet4Address) inetAddress)) {
return ((Inet4Address) inetAddress);
}
}
}
}
}
}
catch (SocketException ex) {
}
catch (Exception ex) {
}
return null;
}

Expand Down Expand Up @@ -75,33 +108,60 @@ public synchronized static String[] getMACAddress(InetAddress ia) {
return macAddress;
}

public static String getLocalIp(Context context) {
//Get wifi service
WifiManager wifiManager = (WifiManager) context
.getSystemService(Context.WIFI_SERVICE);
//Determine if wifi is on
if (!wifiManager.isWifiEnabled()) {
wifiManager.setWifiEnabled(true);
private static int getWifiIpAddress(Context context) {
try {
//Get wifi service
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);

//Determine if wifi is on
if (!wifiManager.isWifiEnabled()) {
wifiManager.setWifiEnabled(true);
}

WifiInfo wifiInfo = wifiManager.getConnectionInfo();
int ip = wifiInfo.getIpAddress();
return ip;
}
catch(Exception e) {
return 0;
}
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
int ipAddress = wifiInfo.getIpAddress();
String ip = intToIp(ipAddress);
}

private static String formatIpAddress(int ip) {
if (ip <= 0) return null;

return ip;
byte[] address = convertIpAddress(ip);

return (address[0] & 0xFF) + "." +
(address[1] & 0xFF) + "." +
(address[2] & 0xFF) + "." +
(address[3] & 0xFF);
}

public static boolean isWifiConnected(Context context) {
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
//Get status
NetworkInfo.State wifi = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI).getState();
//Determine the conditions of wifi connection
if (wifi == NetworkInfo.State.CONNECTED)
return true;
else
return false;
private static byte[] convertIpAddress(int ip) {
if (ip <= 0) return null;

byte[] address = new byte[4];
address[0] = Integer.valueOf((ip ) & 0xFF).byteValue();
address[1] = Integer.valueOf((ip >> 8) & 0xFF).byteValue();
address[2] = Integer.valueOf((ip >> 16) & 0xFF).byteValue();
address[3] = Integer.valueOf((ip >> 24) & 0xFF).byteValue();
return address;
}

private static boolean sameIpAddress(byte[] a, Inet4Address b) {
return (b != null) && sameIpAddress(a, b.getAddress());
}

private static String intToIp(int i) {
return (i & 0xFF) + "." + ((i >> 8) & 0xFF) + "." + ((i >> 16) & 0xFF) + "." + (i >> 24 & 0xFF);
private static boolean sameIpAddress(byte[] a, byte[] b) {
return
(a != null) &&
(b != null) &&
(a.length == 4) &&
(b.length == 4) &&
(a[0] == b[0]) &&
(a[1] == b[1]) &&
(a[2] == b[2]) &&
(a[3] == b[3]);
}
}
4 changes: 2 additions & 2 deletions android-studio-project/constants.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
project.ext {
releaseVersionCode = Integer.parseInt("002004916", 10) //Integer.MAX_VALUE == 2147483647
releaseVersion = '002.00.49-16API'
releaseVersionCode = Integer.parseInt("002005016", 10) //Integer.MAX_VALUE == 2147483647
releaseVersion = '002.00.50-16API'
javaVersion = JavaVersion.VERSION_1_8
minSdkVersion = 16
targetSdkVersion = 29
Expand Down

0 comments on commit 38cf3ec

Please sign in to comment.