Split-off MonitorService
Same motivation as for the parent device: The monitor activity will be quickly destroyed, unless it is connected to a foreground service. By moving the thread and audio listener to a service, this should be avoided.
This commit is contained in:
@@ -26,10 +26,15 @@
|
|||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@android:style/Theme.Holo">
|
android:theme="@android:style/Theme.Holo">
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".ListenService"
|
android:name=".ListenService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
|
<service
|
||||||
|
android:name=".MonitorService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false"/>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".StartActivity"
|
android:name=".StartActivity"
|
||||||
@@ -55,5 +60,4 @@
|
|||||||
android:configChanges="orientation|screenSize"
|
android:configChanges="orientation|screenSize"
|
||||||
android:parentActivityName=".DiscoverActivity" />
|
android:parentActivityName=".DiscoverActivity" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -1,159 +1,76 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of Child Monitor.
|
* This file is part of Child Monitor.
|
||||||
*
|
* <p>
|
||||||
* Child Monitor is free software: you can redistribute it and/or modify
|
* Child Monitor is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
* <p>
|
||||||
* Child Monitor is distributed in the hope that it will be useful,
|
* Child Monitor is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
* <p>
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with Child Monitor. If not, see <http://www.gnu.org/licenses/>.
|
* along with Child Monitor. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package de.rochefort.childmonitor;
|
package de.rochefort.childmonitor;
|
||||||
|
|
||||||
import static de.rochefort.childmonitor.AudioCodecDefines.CODEC;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.media.AudioRecord;
|
import android.content.Intent;
|
||||||
import android.media.MediaRecorder;
|
import android.content.ServiceConnection;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.net.LinkAddress;
|
import android.net.LinkAddress;
|
||||||
import android.net.Network;
|
import android.net.Network;
|
||||||
import android.net.NetworkInfo;
|
import android.net.NetworkInfo;
|
||||||
import android.net.nsd.NsdManager;
|
|
||||||
import android.net.nsd.NsdServiceInfo;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.ServerSocket;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class MonitorActivity extends Activity {
|
public class MonitorActivity extends Activity {
|
||||||
final static String TAG = "ChildMonitor";
|
final static String TAG = "ChildMonitor";
|
||||||
|
private final ServiceConnection connection = new ServiceConnection() {
|
||||||
private NsdManager nsdManager;
|
public void onServiceConnected(ComponentName className, IBinder service) {
|
||||||
|
// This is called when the connection with the service has been
|
||||||
private NsdManager.RegistrationListener registrationListener;
|
// established, giving us the service object we can use to
|
||||||
|
// interact with the service. Because we have bound to a explicit
|
||||||
private ServerSocket currentSocket;
|
// service that we know is running in our own process, we can
|
||||||
|
// cast its IBinder to a concrete class and directly access it.
|
||||||
private Object connectionToken;
|
MonitorService bs = ((MonitorService.MonitorBinder) service).getService();
|
||||||
|
bs.setMonitorActivity(MonitorActivity.this);
|
||||||
private int currentPort;
|
|
||||||
|
|
||||||
private void serviceConnection(Socket socket) {
|
|
||||||
runOnUiThread(() -> {
|
|
||||||
final TextView statusText = findViewById(R.id.textStatus);
|
|
||||||
statusText.setText(R.string.streaming);
|
|
||||||
});
|
|
||||||
|
|
||||||
final int frequency = AudioCodecDefines.FREQUENCY;
|
|
||||||
final int channelConfiguration = AudioCodecDefines.CHANNEL_CONFIGURATION_IN;
|
|
||||||
final int audioEncoding = AudioCodecDefines.ENCODING;
|
|
||||||
|
|
||||||
final int bufferSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding);
|
|
||||||
final AudioRecord audioRecord;
|
|
||||||
try {
|
|
||||||
audioRecord = new AudioRecord(
|
|
||||||
MediaRecorder.AudioSource.MIC,
|
|
||||||
frequency,
|
|
||||||
channelConfiguration,
|
|
||||||
audioEncoding,
|
|
||||||
bufferSize
|
|
||||||
);
|
|
||||||
} catch (SecurityException e) {
|
|
||||||
// This should never happen, we asked for permission before
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final int pcmBufferSize = bufferSize*2;
|
public void onServiceDisconnected(ComponentName className) {
|
||||||
final short[] pcmBuffer = new short[pcmBufferSize];
|
// This is called when the connection with the service has been
|
||||||
final byte[] ulawBuffer = new byte[pcmBufferSize];
|
// unexpectedly disconnected -- that is, its process crashed.
|
||||||
|
// Because it is running in our same process, we should never
|
||||||
try {
|
// see this happen.
|
||||||
audioRecord.startRecording();
|
|
||||||
final OutputStream out = socket.getOutputStream();
|
|
||||||
|
|
||||||
socket.setSendBufferSize(pcmBufferSize);
|
|
||||||
Log.d(TAG, "Socket send buffer size: " + socket.getSendBufferSize());
|
|
||||||
|
|
||||||
while (socket.isConnected() && currentSocket != null && !Thread.currentThread().isInterrupted()) {
|
|
||||||
final int read = audioRecord.read(pcmBuffer, 0, bufferSize);
|
|
||||||
int encoded = CODEC.encode(pcmBuffer, read, ulawBuffer, 0);
|
|
||||||
out.write(ulawBuffer, 0, encoded);
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
};
|
||||||
Log.e(TAG, "Connection failed", e);
|
private boolean shouldUnbind;
|
||||||
} finally {
|
|
||||||
audioRecord.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
Log.i(TAG, "ChildMonitor start");
|
Log.i(TAG, "ChildMonitor start");
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_monitor);
|
setContentView(R.layout.activity_monitor);
|
||||||
|
|
||||||
nsdManager = (NsdManager)this.getSystemService(Context.NSD_SERVICE);
|
|
||||||
currentPort = 10000;
|
|
||||||
currentSocket = null;
|
|
||||||
final Object currentToken = new Object();
|
|
||||||
connectionToken = currentToken;
|
|
||||||
|
|
||||||
new Thread(() -> {
|
|
||||||
while(Objects.equals(connectionToken, currentToken)) {
|
|
||||||
try (ServerSocket serverSocket = new ServerSocket(currentPort)) {
|
|
||||||
currentSocket = serverSocket;
|
|
||||||
// Store the chosen port.
|
|
||||||
final int localPort = serverSocket.getLocalPort();
|
|
||||||
|
|
||||||
// Register the service so that parent devices can
|
|
||||||
// locate the child device
|
|
||||||
registerService(localPort);
|
|
||||||
|
|
||||||
// Wait for a parent to find us and connect
|
|
||||||
try (Socket socket = serverSocket.accept()) {
|
|
||||||
Log.i(TAG, "Connection from parent device received");
|
|
||||||
|
|
||||||
// We now have a client connection.
|
|
||||||
// Unregister so no other clients will
|
|
||||||
// attempt to connect
|
|
||||||
unregisterService();
|
|
||||||
serviceConnection(socket);
|
|
||||||
}
|
|
||||||
} catch(Exception e) {
|
|
||||||
// Just in case
|
|
||||||
currentPort++;
|
|
||||||
Log.e(TAG, "Failed to open server socket. Port increased to " + currentPort, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
|
|
||||||
final TextView addressText = findViewById(R.id.address);
|
final TextView addressText = findViewById(R.id.address);
|
||||||
|
|
||||||
List<String> listenAddresses = getListenAddresses();
|
List<String> listenAddresses = getListenAddresses();
|
||||||
if(!listenAddresses.isEmpty()) {
|
if (!listenAddresses.isEmpty()) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
for (int i = 0; i < listenAddresses.size(); i++) {
|
for (int i = 0; i < listenAddresses.size(); i++) {
|
||||||
String listenAddress = listenAddresses.get(i);
|
String listenAddress = listenAddresses.get(i);
|
||||||
sb.append(listenAddress);
|
sb.append(listenAddress);
|
||||||
if (i != listenAddresses.size() -1) {
|
if (i != listenAddresses.size() - 1) {
|
||||||
sb.append("\n\n");
|
sb.append("\n\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,10 +79,11 @@ public class MonitorActivity extends Activity {
|
|||||||
addressText.setText(R.string.notConnected);
|
addressText.setText(R.string.notConnected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ensureServiceRunningAndBind();
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> getListenAddresses() {
|
private List<String> getListenAddresses() {
|
||||||
ConnectivityManager cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
|
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
List<String> listenAddresses = new ArrayList<>();
|
List<String> listenAddresses = new ArrayList<>();
|
||||||
if (cm != null) {
|
if (cm != null) {
|
||||||
for (Network network : cm.getAllNetworks()) {
|
for (Network network : cm.getAllNetworks()) {
|
||||||
@@ -186,87 +104,37 @@ public class MonitorActivity extends Activity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStop() {
|
public void onDestroy() {
|
||||||
Log.i(TAG, "ChildMonitor stop");
|
doUnbindAndStopService();
|
||||||
|
super.onDestroy();
|
||||||
unregisterService();
|
|
||||||
|
|
||||||
connectionToken = null;
|
|
||||||
if(currentSocket != null) {
|
|
||||||
try {
|
|
||||||
currentSocket.close();
|
|
||||||
currentSocket = null;
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "Failed to close active socket on port "+currentPort);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.onStop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerService(final int port) {
|
void ensureServiceRunningAndBind() {
|
||||||
final NsdServiceInfo serviceInfo = new NsdServiceInfo();
|
final Context context = this;
|
||||||
serviceInfo.setServiceName("ChildMonitor on " + Build.MODEL);
|
final Intent intent = new Intent(context, MonitorService.class);
|
||||||
serviceInfo.setServiceType("_childmonitor._tcp.");
|
ContextCompat.startForegroundService(context, intent);
|
||||||
serviceInfo.setPort(port);
|
// Attempts to establish a connection with the service. We use an
|
||||||
|
// explicit class name because we want a specific service
|
||||||
registrationListener = new NsdManager.RegistrationListener() {
|
// implementation that we know will be running in our own process
|
||||||
@Override
|
// (and thus won't be supporting component replacement by other
|
||||||
public void onServiceRegistered(NsdServiceInfo nsdServiceInfo) {
|
// applications).
|
||||||
// Save the service name. Android may have changed it in order to
|
if (bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
|
||||||
// resolve a conflict, so update the name you initially requested
|
shouldUnbind = true;
|
||||||
// with the name Android actually used.
|
Log.i(TAG, "Bound service");
|
||||||
final String serviceName = nsdServiceInfo.getServiceName();
|
} else {
|
||||||
|
Log.e(TAG, "Error: The requested service doesn't " +
|
||||||
Log.i(TAG, "Service name: " + serviceName);
|
"exist, or this client isn't allowed access to it.");
|
||||||
|
}
|
||||||
MonitorActivity.this.runOnUiThread(() -> {
|
|
||||||
final TextView statusText = findViewById(R.id.textStatus);
|
|
||||||
statusText.setText(R.string.waitingForParent);
|
|
||||||
|
|
||||||
final TextView serviceText = findViewById(R.id.textService);
|
|
||||||
serviceText.setText(serviceName);
|
|
||||||
|
|
||||||
final TextView portText = findViewById(R.id.port);
|
|
||||||
portText.setText(Integer.toString(port));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
void doUnbindAndStopService() {
|
||||||
public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
|
if (shouldUnbind) {
|
||||||
// Registration failed! Put debugging code here to determine why.
|
// Release information about the service's state.
|
||||||
Log.e(TAG, "Registration failed: " + errorCode);
|
unbindService(connection);
|
||||||
}
|
shouldUnbind = false;
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServiceUnregistered(NsdServiceInfo arg0) {
|
|
||||||
// Service has been unregistered. This only happens when you call
|
|
||||||
// NsdManager.unregisterService() and pass in this listener.
|
|
||||||
|
|
||||||
Log.i(TAG, "Unregistering service");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
|
|
||||||
// Unregistration failed. Put debugging code here to determine why.
|
|
||||||
|
|
||||||
Log.e(TAG, "Unregistration failed: " + errorCode);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
nsdManager.registerService(
|
|
||||||
serviceInfo, NsdManager.PROTOCOL_DNS_SD, registrationListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uhregistered the service and assigns the listener
|
|
||||||
* to null.
|
|
||||||
*/
|
|
||||||
private void unregisterService() {
|
|
||||||
if(registrationListener != null) {
|
|
||||||
Log.i(TAG, "Unregistering monitoring service");
|
|
||||||
|
|
||||||
nsdManager.unregisterService(registrationListener);
|
|
||||||
registrationListener = null;
|
|
||||||
}
|
}
|
||||||
|
final Context context = this;
|
||||||
|
final Intent intent = new Intent(context, MonitorService.class);
|
||||||
|
context.stopService(intent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
314
app/src/main/java/de/rochefort/childmonitor/MonitorService.java
Normal file
314
app/src/main/java/de/rochefort/childmonitor/MonitorService.java
Normal file
@@ -0,0 +1,314 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of Child Monitor.
|
||||||
|
* <p>
|
||||||
|
* Child Monitor is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
* <p>
|
||||||
|
* Child Monitor is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
* <p>
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Child Monitor. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package de.rochefort.childmonitor;
|
||||||
|
|
||||||
|
import static de.rochefort.childmonitor.AudioCodecDefines.CODEC;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.media.AudioRecord;
|
||||||
|
import android.media.MediaRecorder;
|
||||||
|
import android.net.nsd.NsdManager;
|
||||||
|
import android.net.nsd.NsdServiceInfo;
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class MonitorService extends Service {
|
||||||
|
final static String TAG = "MonitorService";
|
||||||
|
final static String CHANNEL_ID = TAG;
|
||||||
|
public static final int ID = 1338;
|
||||||
|
private final IBinder binder = new MonitorBinder();
|
||||||
|
private NsdManager nsdManager;
|
||||||
|
private NsdManager.RegistrationListener registrationListener;
|
||||||
|
private ServerSocket currentSocket;
|
||||||
|
private Object connectionToken;
|
||||||
|
private int currentPort;
|
||||||
|
private NotificationManager notificationManager;
|
||||||
|
private Thread monitorThread;
|
||||||
|
private MonitorActivity monitorActivity;
|
||||||
|
|
||||||
|
public void setMonitorActivity(MonitorActivity monitorActivity) {
|
||||||
|
this.monitorActivity = monitorActivity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void serviceConnection(Socket socket) {
|
||||||
|
final MonitorActivity ma = monitorActivity;
|
||||||
|
if (ma != null) {
|
||||||
|
ma.runOnUiThread(() -> {
|
||||||
|
final TextView statusText = monitorActivity.findViewById(R.id.textStatus);
|
||||||
|
statusText.setText(R.string.streaming);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
final int frequency = AudioCodecDefines.FREQUENCY;
|
||||||
|
final int channelConfiguration = AudioCodecDefines.CHANNEL_CONFIGURATION_IN;
|
||||||
|
final int audioEncoding = AudioCodecDefines.ENCODING;
|
||||||
|
|
||||||
|
final int bufferSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding);
|
||||||
|
final AudioRecord audioRecord;
|
||||||
|
try {
|
||||||
|
audioRecord = new AudioRecord(
|
||||||
|
MediaRecorder.AudioSource.MIC,
|
||||||
|
frequency,
|
||||||
|
channelConfiguration,
|
||||||
|
audioEncoding,
|
||||||
|
bufferSize
|
||||||
|
);
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
// This should never happen, we asked for permission before
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
final int pcmBufferSize = bufferSize * 2;
|
||||||
|
final short[] pcmBuffer = new short[pcmBufferSize];
|
||||||
|
final byte[] ulawBuffer = new byte[pcmBufferSize];
|
||||||
|
|
||||||
|
try {
|
||||||
|
audioRecord.startRecording();
|
||||||
|
final OutputStream out = socket.getOutputStream();
|
||||||
|
|
||||||
|
socket.setSendBufferSize(pcmBufferSize);
|
||||||
|
Log.d(TAG, "Socket send buffer size: " + socket.getSendBufferSize());
|
||||||
|
|
||||||
|
while (socket.isConnected() && currentSocket != null && !Thread.currentThread().isInterrupted()) {
|
||||||
|
final int read = audioRecord.read(pcmBuffer, 0, bufferSize);
|
||||||
|
int encoded = CODEC.encode(pcmBuffer, read, ulawBuffer, 0);
|
||||||
|
out.write(ulawBuffer, 0, encoded);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Connection failed", e);
|
||||||
|
} finally {
|
||||||
|
audioRecord.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
Log.i(TAG, "ChildMonitor start");
|
||||||
|
super.onCreate();
|
||||||
|
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||||
|
|
||||||
|
nsdManager = (NsdManager) this.getSystemService(Context.NSD_SERVICE);
|
||||||
|
currentPort = 10000;
|
||||||
|
currentSocket = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
Log.i(TAG, "Received start id " + startId + ": " + intent);
|
||||||
|
// Display a notification about us starting. We put an icon in the status bar.
|
||||||
|
createNotificationChannel();
|
||||||
|
Notification n = buildNotification();
|
||||||
|
startForeground(ID, n);
|
||||||
|
ensureMonitorThread();
|
||||||
|
|
||||||
|
return START_REDELIVER_INTENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureMonitorThread() {
|
||||||
|
Thread mt = monitorThread;
|
||||||
|
if (mt != null && mt.isAlive()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Object currentToken = new Object();
|
||||||
|
connectionToken = currentToken;
|
||||||
|
|
||||||
|
monitorThread = new Thread(() -> {
|
||||||
|
while (Objects.equals(connectionToken, currentToken)) {
|
||||||
|
try (ServerSocket serverSocket = new ServerSocket(currentPort)) {
|
||||||
|
currentSocket = serverSocket;
|
||||||
|
// Store the chosen port.
|
||||||
|
final int localPort = serverSocket.getLocalPort();
|
||||||
|
|
||||||
|
// Register the service so that parent devices can
|
||||||
|
// locate the child device
|
||||||
|
registerService(localPort);
|
||||||
|
|
||||||
|
// Wait for a parent to find us and connect
|
||||||
|
try (Socket socket = serverSocket.accept()) {
|
||||||
|
Log.i(TAG, "Connection from parent device received");
|
||||||
|
|
||||||
|
// We now have a client connection.
|
||||||
|
// Unregister so no other clients will
|
||||||
|
// attempt to connect
|
||||||
|
unregisterService();
|
||||||
|
serviceConnection(socket);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Just in case
|
||||||
|
currentPort++;
|
||||||
|
Log.e(TAG, "Failed to open server socket. Port increased to " + currentPort, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
monitorThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerService(final int port) {
|
||||||
|
final NsdServiceInfo serviceInfo = new NsdServiceInfo();
|
||||||
|
serviceInfo.setServiceName("ChildMonitor on " + Build.MODEL);
|
||||||
|
serviceInfo.setServiceType("_childmonitor._tcp.");
|
||||||
|
serviceInfo.setPort(port);
|
||||||
|
|
||||||
|
registrationListener = new NsdManager.RegistrationListener() {
|
||||||
|
@Override
|
||||||
|
public void onServiceRegistered(NsdServiceInfo nsdServiceInfo) {
|
||||||
|
// Save the service name. Android may have changed it in order to
|
||||||
|
// resolve a conflict, so update the name you initially requested
|
||||||
|
// with the name Android actually used.
|
||||||
|
final String serviceName = nsdServiceInfo.getServiceName();
|
||||||
|
|
||||||
|
Log.i(TAG, "Service name: " + serviceName);
|
||||||
|
|
||||||
|
final MonitorActivity ma = monitorActivity;
|
||||||
|
if (ma != null) {
|
||||||
|
ma.runOnUiThread(() -> {
|
||||||
|
final TextView statusText = ma.findViewById(R.id.textStatus);
|
||||||
|
statusText.setText(R.string.waitingForParent);
|
||||||
|
|
||||||
|
final TextView serviceText = ma.findViewById(R.id.textService);
|
||||||
|
serviceText.setText(serviceName);
|
||||||
|
|
||||||
|
final TextView portText = ma.findViewById(R.id.port);
|
||||||
|
portText.setText(Integer.toString(port));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
|
||||||
|
// Registration failed! Put debugging code here to determine why.
|
||||||
|
Log.e(TAG, "Registration failed: " + errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceUnregistered(NsdServiceInfo arg0) {
|
||||||
|
// Service has been unregistered. This only happens when you call
|
||||||
|
// NsdManager.unregisterService() and pass in this listener.
|
||||||
|
|
||||||
|
Log.i(TAG, "Unregistering service");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
|
||||||
|
// Unregistration failed. Put debugging code here to determine why.
|
||||||
|
|
||||||
|
Log.e(TAG, "Unregistration failed: " + errorCode);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
nsdManager.registerService(
|
||||||
|
serviceInfo, NsdManager.PROTOCOL_DNS_SD, registrationListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister the service and assigns the listener
|
||||||
|
* to null.
|
||||||
|
*/
|
||||||
|
private void unregisterService() {
|
||||||
|
if (registrationListener != null) {
|
||||||
|
Log.i(TAG, "Unregistering monitoring service");
|
||||||
|
|
||||||
|
nsdManager.unregisterService(registrationListener);
|
||||||
|
registrationListener = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createNotificationChannel() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
NotificationChannel serviceChannel = new NotificationChannel(
|
||||||
|
CHANNEL_ID,
|
||||||
|
"Foreground Service Channel",
|
||||||
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
);
|
||||||
|
notificationManager.createNotificationChannel(serviceChannel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Notification buildNotification() {
|
||||||
|
CharSequence text = "Child Device";
|
||||||
|
// Set the info for the views that show in the notification panel.
|
||||||
|
NotificationCompat.Builder b = new NotificationCompat.Builder(this, CHANNEL_ID);
|
||||||
|
b.setSmallIcon(R.drawable.listening_notification) // the status icon
|
||||||
|
.setOngoing(true)
|
||||||
|
.setTicker(text) // the status text
|
||||||
|
.setContentTitle(text); // the label of the entry
|
||||||
|
return b.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
Thread mt = monitorThread;
|
||||||
|
if (mt != null) {
|
||||||
|
mt.interrupt();
|
||||||
|
monitorThread = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
unregisterService();
|
||||||
|
|
||||||
|
connectionToken = null;
|
||||||
|
if (currentSocket != null) {
|
||||||
|
try {
|
||||||
|
currentSocket.close();
|
||||||
|
currentSocket = null;
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Failed to close active socket on port " + currentPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel the persistent notification.
|
||||||
|
int NOTIFICATION = R.string.listening;
|
||||||
|
notificationManager.cancel(NOTIFICATION);
|
||||||
|
|
||||||
|
stopForeground(true);
|
||||||
|
// Tell the user we stopped.
|
||||||
|
Toast.makeText(this, R.string.stopped, Toast.LENGTH_SHORT).show();
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return binder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MonitorBinder extends Binder {
|
||||||
|
MonitorService getService() {
|
||||||
|
return MonitorService.this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user