diff --git a/app/build.gradle b/app/build.gradle
index e8c6100..32bed4d 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,13 +1,13 @@
apply plugin: 'com.android.application'
+apply plugin: 'org.jetbrains.kotlin.android'
android {
- compileSdk 26
+ compileSdk 34
defaultConfig {
applicationId "de.rochefort.childmonitor"
minSdkVersion 21
- //noinspection ExpiredTargetSdkVersion
- targetSdkVersion 26
+ targetSdkVersion 34
versionCode 12
versionName "1.2"
}
@@ -19,9 +19,7 @@ android {
}
}
-
dependencies {
- implementation "com.android.support:support-compat:26.1.0"
}
namespace 'de.rochefort.childmonitor'
lint {
@@ -29,7 +27,16 @@ android {
warning 'MissingTranslation'
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
+ // Sets Java compatibility to Java 21
+ sourceCompatibility JavaVersion.VERSION_21
+ targetCompatibility JavaVersion.VERSION_21
}
+ kotlin {
+ jvmToolchain(21)
+ }
+}
+
+dependencies {
+ implementation 'androidx.core:core:1.12.0'
+ implementation 'androidx.core:core-ktx:1.12.0'
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6c0b841..6725f91 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -20,6 +20,10 @@
android:required="true" />
+
+ .
- */
-package de.rochefort.childmonitor;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.net.nsd.NsdManager;
-import android.net.nsd.NsdServiceInfo;
-import android.net.wifi.WifiManager;
-import android.os.Bundle;
-import android.util.Log;
-import android.widget.ArrayAdapter;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.ListView;
-import android.widget.Toast;
-
-import java.util.Objects;
-
-
-public class DiscoverActivity extends Activity {
- private static final String PREF_KEY_CHILD_DEVICE_ADDRESS = "childDeviceAddress";
- private static final String PREF_KEY_CHILD_DEVICE_PORT = "childDevicePort";
- final String TAG = "ChildMonitor";
-
- private NsdManager nsdManager;
-
- private NsdManager.DiscoveryListener discoveryListener;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- Log.i(TAG, "ChildMonitor start");
-
- nsdManager = (NsdManager)this.getSystemService(Context.NSD_SERVICE);
-
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_discover);
-
- final Button discoverChildButton = findViewById(R.id.discoverChildButton);
- discoverChildButton.setOnClickListener(v -> loadDiscoveryViaMdns());
-
- final Button enterChildAddressButton = findViewById(R.id.enterChildAddressButton);
- enterChildAddressButton.setOnClickListener(v -> loadDiscoveryViaAddress());
- }
-
- private void loadDiscoveryViaMdns() {
- setContentView(R.layout.activity_discover_mdns);
- startServiceDiscovery("_childmonitor._tcp.");
- }
-
- private void loadDiscoveryViaAddress() {
- setContentView(R.layout.activity_discover_address);
-
- final Button connectButton = findViewById(R.id.connectViaAddressButton);
- final EditText addressField = findViewById(R.id.ipAddressField);
- final EditText portField = findViewById(R.id.portField);
- String preferredAddress = getPreferences(MODE_PRIVATE).getString(PREF_KEY_CHILD_DEVICE_ADDRESS, null);
- if (preferredAddress != null && !preferredAddress.isEmpty()) {
- addressField.setText(preferredAddress);
- }
- int preferredPort = getPreferences(MODE_PRIVATE).getInt(PREF_KEY_CHILD_DEVICE_PORT, -1);
- if (preferredPort > 0) {
- portField.setText(String.valueOf(preferredPort));
- } else {
- portField.setText("10000");
- }
-
- connectButton.setOnClickListener(v -> {
- Log.i(TAG, "Connecting to child device via address");
- final String addressString = addressField.getText().toString();
- final String portString = portField.getText().toString();
-
- if(addressString.length() == 0)
- {
- Toast.makeText(DiscoverActivity.this, R.string.invalidAddress, Toast.LENGTH_LONG).show();
- return;
- }
-
- int port;
-
- try {
- port = Integer.parseInt(portString);
- }
- catch(NumberFormatException e)
- {
- Toast.makeText(DiscoverActivity.this, R.string.invalidPort, Toast.LENGTH_LONG).show();
- return;
- }
- SharedPreferences.Editor preferencesEditor = getPreferences(MODE_PRIVATE).edit();
- preferencesEditor.putString(PREF_KEY_CHILD_DEVICE_ADDRESS, addressString);
- preferencesEditor.putInt(PREF_KEY_CHILD_DEVICE_PORT, port);
- preferencesEditor.apply();
- connectToChild(addressString, port, addressString);
- });
- }
-
- @Override
- protected void onDestroy() {
- Log.i(TAG, "ChildMonitoring stop");
-
- if(discoveryListener != null) {
- Log.i(TAG, "Unregistering monitoring service");
-
- nsdManager.stopServiceDiscovery(discoveryListener);
- discoveryListener = null;
- }
-
- super.onDestroy();
- }
-
- public void startServiceDiscovery(final String serviceType) {
- final NsdManager nsdManager = (NsdManager)this.getSystemService(Context.NSD_SERVICE);
- if (nsdManager == null) {
- Log.e(TAG, "Could not obtain nsdManager");
- return;
- }
-
- WifiManager wifi = (WifiManager) this.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
- final Runnable multicastReleaser;
- if (wifi != null) {
- final WifiManager.MulticastLock multicastLock = wifi.createMulticastLock("multicastLock");
- multicastLock.setReferenceCounted(true);
- multicastLock.acquire();
- multicastReleaser = () -> {
- try {
- multicastLock.release();
- } catch (Exception ignored) {
- //dont really care
- }
- };
- } else {
- multicastReleaser = () -> {
- };
- }
-
-
- final ListView serviceTable = findViewById(R.id.ServiceTable);
-
- final ArrayAdapter availableServicesAdapter = new ArrayAdapter<>(this,
- R.layout.available_children_list);
- serviceTable.setAdapter(availableServicesAdapter);
-
- serviceTable.setOnItemClickListener((parent, view, position, id) -> {
- final ServiceInfoWrapper info = (ServiceInfoWrapper) parent.getItemAtPosition(position);
- connectToChild(info.getAddress(), info.getPort(), info.getName());
- });
-
- // Instantiate a new DiscoveryListener
- discoveryListener = new NsdManager.DiscoveryListener() {
- // Called as soon as service discovery begins.
- @Override
- public void onDiscoveryStarted(String regType)
- {
- Log.d(TAG, "Service discovery started");
- }
-
- @Override
- public void onServiceFound(NsdServiceInfo service) {
- // A service was found! Do something with it.
- Log.d(TAG, "Service discovery success: " + service);
-
- if (!service.getServiceType().equals(serviceType)) {
- // Service type is the string containing the protocol and
- // transport layer for this service.
- Log.d(TAG, "Unknown Service Type: " + service.getServiceType());
- } else if (service.getServiceName().contains("ChildMonitor")) {
- NsdManager.ResolveListener resolver = new NsdManager.ResolveListener() {
- @Override
- public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
- // Called when the resolve fails. Use the error code to debug.
- Log.e(TAG, "Resolve failed: error " + errorCode + " for service: " + serviceInfo);
- }
-
- @Override
- public void onServiceResolved(final NsdServiceInfo serviceInfo) {
- Log.i(TAG, "Resolve Succeeded: " + serviceInfo);
-
- DiscoverActivity.this.runOnUiThread(() -> {
- for (int index=0; index < availableServicesAdapter.getCount(); index++) {
- ServiceInfoWrapper item = availableServicesAdapter.getItem(index);
- if (item != null && item.matches(serviceInfo)) {
- // Prevent inserting duplicates
- return;
- }
- }
- availableServicesAdapter.add(new ServiceInfoWrapper(serviceInfo));
- });
- }
- };
-
- DiscoverActivity.this.nsdManager.resolveService(service, resolver);
- } else {
- Log.d(TAG, "Unknown Service name: " + service.getServiceName());
- }
- }
-
- @Override
- public void onServiceLost(NsdServiceInfo service) {
- // When the network service is no longer available.
- // Internal bookkeeping code goes here.
- Log.e(TAG, "Service lost: " + service);
- multicastReleaser.run();
- }
-
- @Override
- public void onDiscoveryStopped(String serviceType) {
- Log.i(TAG, "Discovery stopped: " + serviceType);
- multicastReleaser.run();
- }
-
- @Override
- public void onStartDiscoveryFailed(String serviceType, int errorCode) {
- Log.e(TAG, "Discovery failed: Error code: " + errorCode);
- nsdManager.stopServiceDiscovery(this);
- multicastReleaser.run();
- }
-
- @Override
- public void onStopDiscoveryFailed(String serviceType, int errorCode) {
- Log.e(TAG, "Discovery failed: Error code: " + errorCode);
- nsdManager.stopServiceDiscovery(this);
- multicastReleaser.run();
- }
- };
-
- nsdManager.discoverServices(
- serviceType, NsdManager.PROTOCOL_DNS_SD, discoveryListener
- );
- }
-
- private void connectToChild(final String address, final int port, final String name) {
- final Intent i = new Intent(getApplicationContext(), ListenActivity.class);
- final Bundle b = new Bundle();
- b.putString("address", address);
- b.putInt("port", port);
- b.putString("name", name);
- i.putExtras(b);
- startActivity(i);
- }
-}
-
-class ServiceInfoWrapper {
- private final NsdServiceInfo info;
- public ServiceInfoWrapper(NsdServiceInfo info)
- {
- this.info = info;
- }
-
- public boolean matches(NsdServiceInfo other) {
- return Objects.equals(this.info.getHost(), other.getHost()) && this.info.getPort() == other.getPort();
- }
-
- public String getAddress()
- {
- return info.getHost().getHostAddress();
- }
-
- public int getPort()
- {
- return info.getPort();
- }
-
- public String getName() {
- // If there is more than one service with the same name on the network, it will
- // have a number at the end, but will appear as the following:
- // "ChildMonitor\\032(number)
- // or
- // "ChildMonitor\032(number)
- // Replace \\032 and \032 with a " "
- String serviceName = info.getServiceName();
- serviceName = serviceName.replace("\\\\032", " ");
- serviceName = serviceName.replace("\\032", " ");
- return serviceName;
- }
-
- @Override
- public String toString()
- {
- return getName();
- }
-}
diff --git a/app/src/main/java/de/rochefort/childmonitor/ListenActivity.java b/app/src/main/java/de/rochefort/childmonitor/ListenActivity.java
deleted file mode 100644
index 557e823..0000000
--- a/app/src/main/java/de/rochefort/childmonitor/ListenActivity.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * This file is part of Child Monitor.
- *
- * 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.
- *
- * 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.
- *
- * You should have received a copy of the GNU General Public License
- * along with Child Monitor. If not, see .
- */
-package de.rochefort.childmonitor;
-
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.media.AudioManager;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.support.v4.content.ContextCompat;
-import android.util.Log;
-import android.widget.TextView;
-import android.widget.Toast;
-
-public class ListenActivity extends Activity {
- private static final String TAG = "ListenActivity";
-
- // Don't attempt to unbind from the service unless the client has received some
- // information about the service's state.
- private boolean shouldUnbind;
-
- private final ServiceConnection connection = new ServiceConnection() {
- public void onServiceConnected(ComponentName className, IBinder service) {
- // This is called when the connection with the service has been
- // established, giving us the service object we can use to
- // interact with the service. Because we have bound to a explicit
- // service that we know is running in our own process, we can
- // cast its IBinder to a concrete class and directly access it.
- ListenService bs = ((ListenService.ListenBinder) service).getService();
-
- Toast.makeText(ListenActivity.this, R.string.connect,
- Toast.LENGTH_SHORT).show();
- final TextView connectedText = findViewById(R.id.connectedTo);
- connectedText.setText(bs.getChildDeviceName());
- final VolumeView volumeView = findViewById(R.id.volume);
- volumeView.setVolumeHistory(bs.getVolumeHistory());
- bs.setUpdateCallback(volumeView::postInvalidate);
- bs.setErrorCallback(ListenActivity.this::postErrorMessage);
- }
-
- public void onServiceDisconnected(ComponentName className) {
- // This is called when the connection with the service has been
- // unexpectedly disconnected -- that is, its process crashed.
- // Because it is running in our same process, we should never
- // see this happen.
- Toast.makeText(ListenActivity.this, R.string.disconnected,
- Toast.LENGTH_SHORT).show();
- }
- };
-
-
- void ensureServiceRunningAndBind(Bundle bundle) {
- final Context context = this;
- final Intent intent = new Intent(context, ListenService.class);
- if (bundle != null) {
- intent.putExtra("name", bundle.getString("name"));
- intent.putExtra("address", bundle.getString("address"));
- intent.putExtra("port", bundle.getInt("port"));
- ContextCompat.startForegroundService(context, intent);
- }
- // Attempts to establish a connection with the service. We use an
- // explicit class name because we want a specific service
- // implementation that we know will be running in our own process
- // (and thus won't be supporting component replacement by other
- // applications).
- if (bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
- shouldUnbind = true;
- Log.i(TAG, "Bound listen service");
- } else {
- Log.e(TAG, "Error: The requested service doesn't " +
- "exist, or this client isn't allowed access to it.");
- }
- }
-
- void doUnbindAndStopService() {
- if (shouldUnbind) {
- // Release information about the service's state.
- unbindService(connection);
- shouldUnbind = false;
- }
- final Context context = this;
- final Intent intent = new Intent(context, ListenService.class);
- context.stopService(intent);
- }
-
- public void postErrorMessage() {
- TextView status = findViewById(R.id.textStatus);
- status.post(() -> status.setText(R.string.disconnected));
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- final Bundle bundle = getIntent().getExtras();
- ensureServiceRunningAndBind(bundle);
-
- setVolumeControlStream(AudioManager.STREAM_MUSIC);
- setContentView(R.layout.activity_listen);
-
- final TextView statusText = findViewById(R.id.textStatus);
- statusText.setText(R.string.listening);
- }
-
- @Override
- public void onDestroy() {
- doUnbindAndStopService();
- super.onDestroy();
- }
-}
diff --git a/app/src/main/java/de/rochefort/childmonitor/ListenService.java b/app/src/main/java/de/rochefort/childmonitor/ListenService.java
deleted file mode 100644
index badacec..0000000
--- a/app/src/main/java/de/rochefort/childmonitor/ListenService.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * This file is part of Child Monitor.
- *
- * 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.
- *
- * 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.
- *
- * You should have received a copy of the GNU General Public License
- * along with Child Monitor. If not, see .
- */
-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.Intent;
-import android.media.AudioManager;
-import android.media.AudioTrack;
-import android.media.MediaPlayer;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.support.v4.app.NotificationCompat;
-import android.util.Log;
-import android.widget.Toast;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.Socket;
-
-public class ListenService extends Service {
- private static final String TAG = "ListenService";
- public static final String CHANNEL_ID = TAG;
- public static final int ID = 902938409;
-
- private final int frequency = AudioCodecDefines.FREQUENCY;
- private final int channelConfiguration = AudioCodecDefines.CHANNEL_CONFIGURATION_OUT;
- private final int audioEncoding = AudioCodecDefines.ENCODING;
- private final int bufferSize = AudioTrack.getMinBufferSize(frequency, channelConfiguration, audioEncoding);
- private final int byteBufferSize = bufferSize*2;
-
- private final IBinder binder = new ListenBinder();
- private NotificationManager notificationManager;
- private Thread listenThread;
-
- private final VolumeHistory volumeHistory = new VolumeHistory(16_384);
- private String childDeviceName;
-
- public ListenService() {
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
- }
-
- @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();
- Bundle extras = intent.getExtras();
- if (extras != null) {
- String name = extras.getString("name");
- childDeviceName = name;
- Notification n = buildNotification(name);
- startForeground(ID, n);
- String address = extras.getString("address");
- int port = extras.getInt("port");
- doListen(address, port);
- }
-
- return START_REDELIVER_INTENT;
- }
-
- @Override
- public void onDestroy() {
- Thread lt = listenThread;
- if (lt != null) {
- lt.interrupt();
- listenThread = null;
- }
-
- // 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();
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return binder;
- }
-
- public VolumeHistory getVolumeHistory() {
- return volumeHistory;
- }
-
- private Notification buildNotification(String name) {
- // In this sample, we'll use the same text for the ticker and the expanded notification
- CharSequence text = getText(R.string.listening);
-
- // The PendingIntent to launch our activity if the user selects this notification
- PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
- new Intent(this, ListenActivity.class), PendingIntent.FLAG_IMMUTABLE);
-
- // 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
- .setContentText(name) // the contents of the entry
- .setContentIntent(contentIntent);
- return b.build();
- }
-
-
- 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);
- }
- }
-
- public void setErrorCallback(Runnable errorCallback) {
- this.mErrorCallback = errorCallback;
- }
-
- public void setUpdateCallback(Runnable updateCallback) {
- this.mUpdateCallback = updateCallback;
- }
-
- public class ListenBinder extends Binder {
- ListenService getService() {
- return ListenService.this;
- }
- }
-
- private Runnable mErrorCallback;
- private Runnable mUpdateCallback;
-
- private void doListen(String address, int port) {
- Thread lt = new Thread(() -> {
- try {
- final Socket socket = new Socket(address, port);
- streamAudio(socket);
- } catch (IOException e) {
- Log.e(TAG, "Failed to stream audio", e);
- }
-
- if (!Thread.currentThread().isInterrupted()) {
- // If this thread has not been interrupted, likely something
- // bad happened with the connection to the child device. Play
- // an alert to notify the user that the connection has been
- // interrupted.
- playAlert();
-
- final Runnable errorCallback = mErrorCallback;
- if (errorCallback != null) {
- errorCallback.run();
- }
- }
- });
- listenThread = lt;
- lt.start();
- }
-
-
- private void streamAudio(final Socket socket) throws IllegalArgumentException, IllegalStateException, IOException {
- Log.i(TAG, "Setting up stream");
-
- final AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
- frequency,
- channelConfiguration,
- audioEncoding,
- bufferSize,
- AudioTrack.MODE_STREAM);
-
- final InputStream is = socket.getInputStream();
- int read = 0;
-
- audioTrack.play();
-
- try {
- final byte [] readBuffer = new byte[byteBufferSize];
- final short [] decodedBuffer = new short[byteBufferSize*2];
-
- while (socket.isConnected() && read != -1 && !Thread.currentThread().isInterrupted()) {
- read = is.read(readBuffer);
- int decoded = CODEC.decode(decodedBuffer, readBuffer, read, 0);
-
- if (decoded > 0) {
- audioTrack.write(decodedBuffer, 0, decoded);
- short[] decodedBytes = new short[decoded];
- System.arraycopy(decodedBuffer, 0, decodedBytes, 0, decoded);
- volumeHistory.onAudioData(decodedBytes);
- final Runnable updateCallback = mUpdateCallback;
- if (updateCallback != null) {
- updateCallback.run();
- }
- }
- }
- } catch (Exception e) {
- Log.e(TAG, "Connection failed", e);
- } finally {
- audioTrack.stop();
- socket.close();
- }
- }
-
-
- private void playAlert() {
- final MediaPlayer mp = MediaPlayer.create(this, R.raw.upward_beep_chromatic_fifths);
- if(mp != null) {
- Log.i(TAG, "Playing alert");
- mp.setOnCompletionListener(MediaPlayer::release);
- mp.start();
- } else {
- Log.e(TAG, "Failed to play alert");
- }
- }
-
- public String getChildDeviceName() {
- return childDeviceName;
- }
-}
diff --git a/app/src/main/java/de/rochefort/childmonitor/MonitorActivity.java b/app/src/main/java/de/rochefort/childmonitor/MonitorActivity.java
deleted file mode 100644
index 625697a..0000000
--- a/app/src/main/java/de/rochefort/childmonitor/MonitorActivity.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * This file is part of Child Monitor.
- *
- * 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.
- *
- * 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.
- *
- * You should have received a copy of the GNU General Public License
- * along with Child Monitor. If not, see .
- */
-package de.rochefort.childmonitor;
-
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.net.ConnectivityManager;
-import android.net.LinkAddress;
-import android.net.Network;
-import android.net.NetworkInfo;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.support.v4.content.ContextCompat;
-import android.util.Log;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import java.net.InetAddress;
-import java.util.ArrayList;
-import java.util.List;
-
-public class MonitorActivity extends Activity {
- final static String TAG = "ChildMonitor";
- private final ServiceConnection connection = new ServiceConnection() {
- public void onServiceConnected(ComponentName className, IBinder service) {
- // This is called when the connection with the service has been
- // established, giving us the service object we can use to
- // interact with the service. Because we have bound to an explicit
- // service that we know is running in our own process, we can
- // cast its IBinder to a concrete class and directly access it.
- MonitorService bs = ((MonitorService.MonitorBinder) service).getService();
- bs.setMonitorActivity(MonitorActivity.this);
- }
-
- public void onServiceDisconnected(ComponentName className) {
- // This is called when the connection with the service has been
- // unexpectedly disconnected -- that is, its process crashed.
- // Because it is running in our same process, we should never
- // see this happen.
- Toast.makeText(MonitorActivity.this, R.string.disconnected,
- Toast.LENGTH_SHORT).show();
- }
- };
- private boolean shouldUnbind;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- Log.i(TAG, "ChildMonitor start");
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_monitor);
- final TextView addressText = findViewById(R.id.address);
-
- List listenAddresses = getListenAddresses();
- if (!listenAddresses.isEmpty()) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < listenAddresses.size(); i++) {
- String listenAddress = listenAddresses.get(i);
- sb.append(listenAddress);
- if (i != listenAddresses.size() - 1) {
- sb.append("\n\n");
- }
- }
- addressText.setText(sb.toString());
- } else {
- addressText.setText(R.string.notConnected);
- }
-
- ensureServiceRunningAndBind();
- }
-
- private List getListenAddresses() {
- ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
- List listenAddresses = new ArrayList<>();
- if (cm != null) {
- for (Network network : cm.getAllNetworks()) {
- NetworkInfo networkInfo = cm.getNetworkInfo(network);
- boolean connected = networkInfo.isConnected();
- if (connected) {
- List linkAddresses = cm.getLinkProperties(network).getLinkAddresses();
- for (LinkAddress linkAddress : linkAddresses) {
- InetAddress address = linkAddress.getAddress();
- if (!address.isLinkLocalAddress() && !address.isLoopbackAddress()) {
- listenAddresses.add(address.getHostAddress() + " (" + networkInfo.getTypeName() + ")");
- }
- }
- }
- }
- }
- return listenAddresses;
- }
-
- @Override
- public void onDestroy() {
- doUnbindAndStopService();
- super.onDestroy();
- }
-
- void ensureServiceRunningAndBind() {
- final Context context = this;
- final Intent intent = new Intent(context, MonitorService.class);
- ContextCompat.startForegroundService(context, intent);
- // Attempts to establish a connection with the service. We use an
- // explicit class name because we want a specific service
- // implementation that we know will be running in our own process
- // (and thus won't be supporting component replacement by other
- // applications).
- if (bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
- shouldUnbind = true;
- Log.i(TAG, "Bound monitor service");
- } else {
- Log.e(TAG, "Error: The requested service doesn't " +
- "exist, or this client isn't allowed access to it.");
- }
- }
-
- void doUnbindAndStopService() {
- if (shouldUnbind) {
- // Release information about the service's state.
- unbindService(connection);
- shouldUnbind = false;
- }
- final Context context = this;
- final Intent intent = new Intent(context, MonitorService.class);
- context.stopService(intent);
- }
-}
diff --git a/app/src/main/java/de/rochefort/childmonitor/MonitorService.java b/app/src/main/java/de/rochefort/childmonitor/MonitorService.java
deleted file mode 100644
index 9ac1642..0000000
--- a/app/src/main/java/de/rochefort/childmonitor/MonitorService.java
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * This file is part of Child Monitor.
- *
- * 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.
- *
- * 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.
- *
- * You should have received a copy of the GNU General Public License
- * along with Child Monitor. If not, see .
- */
-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.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;
-
- mt = 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) {
- if (Objects.equals(connectionToken, currentToken)) {
- // Just in case
- currentPort++;
- Log.e(TAG, "Failed to open server socket. Port increased to " + currentPort, e);
- }
- }
- }
- });
- monitorThread = mt;
- mt.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);
- }
-
- private void unregisterService() {
- NsdManager.RegistrationListener currentListener = registrationListener;
- if (currentListener != null) {
- Log.i(TAG, "Unregistering monitoring service");
-
- nsdManager.unregisterService(currentListener);
- 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;
- }
- }
-
-}
diff --git a/app/src/main/java/de/rochefort/childmonitor/StartActivity.java b/app/src/main/java/de/rochefort/childmonitor/StartActivity.java
deleted file mode 100644
index a889e68..0000000
--- a/app/src/main/java/de/rochefort/childmonitor/StartActivity.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * This file is part of Child Monitor.
- *
- * 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.
- *
- * 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.
- *
- * You should have received a copy of the GNU General Public License
- * along with Child Monitor. If not, see .
- */
-package de.rochefort.childmonitor;
-
-import android.Manifest;
-import android.app.Activity;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.os.Bundle;
-import android.support.v4.app.ActivityCompat;
-import android.support.v4.content.ContextCompat;
-import android.util.Log;
-import android.widget.Button;
-
-public class StartActivity extends Activity {
- static final String TAG = "ChildMonitor";
- private final static int PERMISSIONS_REQUEST_RECORD_AUDIO = 298349824;
- private final static int PERMISSIONS_REQUEST_MULTICAST = 298349825;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- Log.i(TAG, "ChildMonitor launched");
-
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_start);
-
- final Button monitorButton = findViewById(R.id.useChildDevice);
- monitorButton.setOnClickListener(v -> {
- Log.i(TAG, "Starting up monitor");
-
- if (isAudioRecordingPermissionGranted()) {
- startActivity(new Intent(getApplicationContext(), MonitorActivity.class));
- } else {
- requestAudioPermission();
- }
- });
-
- final Button connectButton = findViewById(R.id.useParentDevice);
- connectButton.setOnClickListener(v -> {
- Log.i(TAG, "Starting connection activity");
- if (isMulticastPermissionGranted()) {
- Intent i = new Intent(getApplicationContext(), DiscoverActivity.class);
- startActivity(i);
- } else {
- requestMulticastPermission();
- }
- });
- }
-
- private boolean isMulticastPermissionGranted() {
- return ContextCompat.checkSelfPermission(StartActivity.this, Manifest.permission.CHANGE_WIFI_MULTICAST_STATE)
- == PackageManager.PERMISSION_GRANTED;
- }
-
- private boolean isAudioRecordingPermissionGranted() {
- return ContextCompat.checkSelfPermission(StartActivity.this, Manifest.permission.RECORD_AUDIO)
- == PackageManager.PERMISSION_GRANTED;
- }
-
- private void requestAudioPermission() {
- ActivityCompat.requestPermissions(StartActivity.this,
- new String[]{Manifest.permission.RECORD_AUDIO},
- PERMISSIONS_REQUEST_RECORD_AUDIO);
- }
-
- private void requestMulticastPermission() {
- ActivityCompat.requestPermissions(StartActivity.this,
- new String[]{Manifest.permission.CHANGE_WIFI_MULTICAST_STATE},
- PERMISSIONS_REQUEST_MULTICAST);
- }
-
- @Override
- public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
- if (requestCode == PERMISSIONS_REQUEST_RECORD_AUDIO && grantResults.length > 0
- && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- startActivity(new Intent(getApplicationContext(), MonitorActivity.class));
- } else if (requestCode == PERMISSIONS_REQUEST_MULTICAST) {
- // its okay if the permission was denied... the user will have to type the address manually
- startActivity(new Intent(getApplicationContext(), DiscoverActivity.class));
- }
- }
-}
diff --git a/app/src/main/java/de/rochefort/childmonitor/VolumeHistory.java b/app/src/main/java/de/rochefort/childmonitor/VolumeHistory.java
deleted file mode 100644
index 6295a9c..0000000
--- a/app/src/main/java/de/rochefort/childmonitor/VolumeHistory.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * This file is part of Child Monitor.
- *
- * 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.
- *
- * 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.
- *
- * You should have received a copy of the GNU General Public License
- * along with Child Monitor. If not, see .
- */
-package de.rochefort.childmonitor;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.support.v4.util.CircularArray;
-
-public class VolumeHistory {
- private double maxVolume = 0.25;
-
- private double volumeNorm = 1.0 / maxVolume;
- private final CircularArray historyData;
- private final int maxHistory;
-
- private final Handler uiHandler;
-
- VolumeHistory(int maxHistory) {
- uiHandler = new Handler(Looper.getMainLooper());
- this.maxHistory = maxHistory;
- historyData = new CircularArray<>(maxHistory);
- }
-
-
- public double getVolumeNorm() {
- return volumeNorm;
- }
-
- public double get(int i) {
- return historyData.get(i);
- }
-
- public int size() {
- return historyData.size();
- }
-
- private void addLast(double volume) {
- // schedule editing of member vars on the ui event loop to avoid concurrency problems
- uiHandler.post(() -> {
- if (volume > maxVolume) {
- maxVolume = volume;
- volumeNorm = 1.0 / volume;
- }
- historyData.addLast(volume);
- historyData.removeFromStart(historyData.size() - maxHistory);
- });
- }
-
- public void onAudioData(short[] data) {
- if (data.length < 1) {
- return;
- }
-
- final double scale = 1.0 / 128.0;
- double sum = 0;
- for (final short datum : data) {
- final double rel = datum * scale;
- sum += rel * rel;
- }
- final double volume = sum / data.length;
- addLast(volume);
- }
-}
diff --git a/app/src/main/java/de/rochefort/childmonitor/VolumeView.java b/app/src/main/java/de/rochefort/childmonitor/VolumeView.java
deleted file mode 100644
index cf3c448..0000000
--- a/app/src/main/java/de/rochefort/childmonitor/VolumeView.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * This file is part of Child Monitor.
- *
- * 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.
- *
- * 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.
- *
- * You should have received a copy of the GNU General Public License
- * along with Child Monitor. If not, see .
- */
-package de.rochefort.childmonitor;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.support.annotation.Nullable;
-import android.util.AttributeSet;
-import android.view.View;
-
-public class VolumeView extends View {
- private final Paint paint;
- private VolumeHistory volumeHistory;
-
- public VolumeView(Context context) {
- super(context);
- this.paint = initPaint();
- }
-
- public VolumeView(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- this.paint = initPaint();
- }
-
- public VolumeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- this.paint = initPaint();
- }
-
- private Paint initPaint() {
- Paint paint = new Paint();
- paint.setColor(Color.rgb(255, 127, 0));
- return paint;
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- final VolumeHistory volumeHistory = this.volumeHistory;
- if (volumeHistory == null) {
- return;
- }
-
- final int height = getHeight();
- final int width = getWidth();
-
- final int size = volumeHistory.size(); // Size is at most width
- final double volumeNorm = volumeHistory.getVolumeNorm();
- final double relativeBrightness;
- if (size > 0) {
- final double normalizedVolume = volumeHistory.get(size - 1);
- relativeBrightness = Math.max(0.3, normalizedVolume);
- } else {
- relativeBrightness = 0.3;
- }
- int blue;
- int rest;
- if (relativeBrightness > 0.5) {
- blue = 255;
- rest = (int) (2 * 255 * (relativeBrightness - 0.5));
- } else {
- blue = (int) (255 * (relativeBrightness - 0.2) / 0.3);
- rest = 0;
- }
- final int rgb = Color.rgb(rest, rest, blue);
- canvas.drawColor(rgb);
- if (size == 0) {
- return;
- }
- final double margins = height * 0.1;
- final double graphHeight = height - 2.0 * margins;
- int leftMost = Math.max(0, volumeHistory.size() - width);
- final double graphScale = graphHeight * volumeNorm;
-
- int xPrev = 0;
- int yPrev = ((int) (margins + graphHeight - volumeHistory.get(leftMost) * graphScale));
- int length = Math.min(size, width);
- for (int xNext = 1; xNext < length-1; ++xNext) {
- int yNext = (int) (margins + graphHeight - volumeHistory.get(leftMost + xNext) * graphScale);
- canvas.drawLine(xPrev, yPrev, xNext, yNext, paint);
- xPrev = xNext;
- yPrev = yNext;
- }
- }
-
- public void setVolumeHistory(VolumeHistory volumeHistory) {
- this.volumeHistory = volumeHistory;
- }
-}
diff --git a/app/src/main/java/de/rochefort/childmonitor/audio/G711UCodec.java b/app/src/main/java/de/rochefort/childmonitor/audio/G711UCodec.java
deleted file mode 100644
index 8736a57..0000000
--- a/app/src/main/java/de/rochefort/childmonitor/audio/G711UCodec.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * Taken from https://android.googlesource.com/platform/external/nist-sip/+/6f95fdeab4481188b6260041b41d1db12b101266/src/com/android/sip/media/G711UCodec.java
- *
- */
-package de.rochefort.childmonitor.audio;
-/**
- * G.711 codec. This class provides u-law conversion.
- */
-public class G711UCodec {
- // s00000001wxyz...s000wxyz
- // s0000001wxyza...s001wxyz
- // s000001wxyzab...s010wxyz
- // s00001wxyzabc...s011wxyz
- // s0001wxyzabcd...s100wxyz
- // s001wxyzabcde...s101wxyz
- // s01wxyzabcdef...s110wxyz
- // s1wxyzabcdefg...s111wxyz
- private static final byte[] table13to8 = new byte[8192];
- private static final short[] table8to16 = new short[256];
- static {
- // b13 --> b8
- for (int p = 1, q = 0; p <= 0x80; p <<= 1, q+=0x10) {
- for (int i = 0, j = (p << 4) - 0x10; i < 16; i++, j += p) {
- int v = (i + q) ^ 0x7F;
- byte value1 = (byte) v;
- byte value2 = (byte) (v + 128);
- for (int m = j, e = j + p; m < e; m++) {
- table13to8[m] = value1;
- table13to8[8191 - m] = value2;
- }
- }
- }
- // b8 --> b16
- for (int q = 0; q <= 7; q++) {
- for (int i = 0, m = (q << 4); i < 16; i++, m++) {
- int v = (((i + 0x10) << q) - 0x10) << 3;
- table8to16[m ^ 0x7F] = (short) v;
- table8to16[(m ^ 0x7F) + 128] = (short) (65536 - v);
- }
- }
- }
- public int decode(short[] b16, byte[] ulaw, int count, int offset) {
- for (int i = 0, j = offset; i < count; i++, j++) {
- b16[i] = table8to16[ulaw[j] & 0xFF];
- }
- return count;
- }
- public int encode(short[] b16, int count, byte[] b8, int offset) {
- for (int i = 0, j = offset; i < count; i++, j++) {
- b8[j] = table13to8[(b16[i] >> 4) & 0x1FFF];
- }
- return count;
- }
- public int getSampleCount(int frameSize) {
- return frameSize;
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/de/rochefort/childmonitor/AudioCodecDefines.java b/app/src/main/kotlin/de/rochefort/childmonitor/AudioCodecDefines.kt
similarity index 53%
rename from app/src/main/java/de/rochefort/childmonitor/AudioCodecDefines.java
rename to app/src/main/kotlin/de/rochefort/childmonitor/AudioCodecDefines.kt
index fc801f2..3e75cf0 100644
--- a/app/src/main/java/de/rochefort/childmonitor/AudioCodecDefines.java
+++ b/app/src/main/kotlin/de/rochefort/childmonitor/AudioCodecDefines.kt
@@ -14,20 +14,15 @@
* You should have received a copy of the GNU General Public License
* along with Child Monitor. If not, see .
*/
-package de.rochefort.childmonitor;
+package de.rochefort.childmonitor
-import android.media.AudioFormat;
+import android.media.AudioFormat
+import de.rochefort.childmonitor.audio.G711UCodec
-import de.rochefort.childmonitor.audio.G711UCodec;
-
-public class AudioCodecDefines {
- public static final int FREQUENCY = 8000;
- public static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT;
- public static final G711UCodec CODEC = new G711UCodec();
- public static final int CHANNEL_CONFIGURATION_IN = AudioFormat.CHANNEL_IN_MONO;
- public static final int CHANNEL_CONFIGURATION_OUT = AudioFormat.CHANNEL_OUT_MONO;
-
- private AudioCodecDefines() {
- throw new IllegalStateException("Do not instantiate!");
- }
+object AudioCodecDefines {
+ const val FREQUENCY = 8000
+ const val ENCODING = AudioFormat.ENCODING_PCM_16BIT
+ val CODEC = G711UCodec()
+ const val CHANNEL_CONFIGURATION_IN = AudioFormat.CHANNEL_IN_MONO
+ const val CHANNEL_CONFIGURATION_OUT = AudioFormat.CHANNEL_OUT_MONO
}
diff --git a/app/src/main/kotlin/de/rochefort/childmonitor/DiscoverActivity.kt b/app/src/main/kotlin/de/rochefort/childmonitor/DiscoverActivity.kt
new file mode 100644
index 0000000..1f46d62
--- /dev/null
+++ b/app/src/main/kotlin/de/rochefort/childmonitor/DiscoverActivity.kt
@@ -0,0 +1,235 @@
+/*
+ * This file is part of Child Monitor.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Child Monitor. If not, see .
+ */
+package de.rochefort.childmonitor
+
+import android.app.Activity
+import android.content.Intent
+import android.net.nsd.NsdManager
+import android.net.nsd.NsdManager.DiscoveryListener
+import android.net.nsd.NsdServiceInfo
+import android.net.wifi.WifiManager
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import android.widget.AdapterView
+import android.widget.AdapterView.OnItemClickListener
+import android.widget.ArrayAdapter
+import android.widget.Button
+import android.widget.EditText
+import android.widget.ListView
+import android.widget.Toast
+
+class DiscoverActivity : Activity() {
+ private lateinit var nsdManager: NsdManager
+ private var discoveryListener: DiscoveryListener? = null
+ override fun onCreate(savedInstanceState: Bundle?) {
+ Log.i(TAG, "ChildMonitor start")
+ this.nsdManager = this.getSystemService(NSD_SERVICE) as NsdManager
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_discover)
+ val discoverChildButton = findViewById