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