listOfPeers) {
- for (WifiP2pDevice device : listOfPeers) {
- if (device.deviceName.endsWith("flyinn-" + nameNum)) {
- this.connectToPeer(device);
+ public void onRequestPermissionsResult(
+ int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+
+ if (requestCode != REQUEST_CODE_REQUIRED_PERMISSIONS) {
+ return;
+ }
+
+ for (int grantResult : grantResults) {
+ if (grantResult == PackageManager.PERMISSION_DENIED) {
+ Log.w("flyinn.ShowCode", "Permissions necessary for connections were not granted.");
+ mToast.setText(R.string.nearby_missing_permissions);
+ mToast.show();
+ finish();
+ }
+ }
+ recreate();
+ }
+
+ /**
+ * Determines whether the FlyInn server app has the necessary permissions to run nearby.
+ *
+ * @return True if the app was granted all the permissions, false otherwise
+ */
+ public boolean hasPermissions(String[] permissions) {
+ for (String permission : permissions) {
+ if (ContextCompat.checkSelfPermission(this, permission)
+ != PackageManager.PERMISSION_GRANTED) {
+ return false;
}
}
+ return true;
}
}
diff --git a/app/src/main/java/com/amos/flyinn/nearbyservice/NearbyServer.java b/app/src/main/java/com/amos/flyinn/nearbyservice/NearbyServer.java
new file mode 100644
index 00000000..8761c5b9
--- /dev/null
+++ b/app/src/main/java/com/amos/flyinn/nearbyservice/NearbyServer.java
@@ -0,0 +1,182 @@
+package com.amos.flyinn.nearbyservice;
+
+import android.Manifest;
+import android.support.annotation.NonNull;
+import android.util.Log;
+
+import com.google.android.gms.nearby.Nearby;
+import com.google.android.gms.nearby.connection.AdvertisingOptions;
+import com.google.android.gms.nearby.connection.ConnectionInfo;
+import com.google.android.gms.nearby.connection.ConnectionLifecycleCallback;
+import com.google.android.gms.nearby.connection.ConnectionResolution;
+import com.google.android.gms.nearby.connection.ConnectionsClient;
+import com.google.android.gms.nearby.connection.ConnectionsStatusCodes;
+import com.google.android.gms.nearby.connection.Payload;
+import com.google.android.gms.nearby.connection.PayloadCallback;
+import com.google.android.gms.nearby.connection.PayloadTransferUpdate;
+import com.google.android.gms.nearby.connection.Strategy;
+
+/**
+ * Handle creating a nearby service that will advertise itself and manage
+ * incoming connections.
+ */
+class NearbyServer {
+ public static final String TAG = NearbyServer.class.getPackage().getName();
+ /**
+ * Required permissions for Nearby connections
+ */
+ public static final String[] REQUIRED_PERMISSIONS =
+ new String[]{
+ Manifest.permission.BLUETOOTH,
+ Manifest.permission.BLUETOOTH_ADMIN,
+ Manifest.permission.ACCESS_WIFI_STATE,
+ Manifest.permission.CHANGE_WIFI_STATE,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ };
+ private static final int REQUEST_CODE_REQUIRED_PERMISSIONS = 1;
+
+ /**
+ * 1-to-1 since a device will be connected to only one other device at most.
+ */
+ private static final Strategy STRATEGY = Strategy.P2P_POINT_TO_POINT;
+
+ /**
+ * Connection manager for the connection to FlyInn clients.
+ */
+ protected ConnectionsClient connectionsClient;
+ private final String serviceName = "nearby_server";
+ private final String serviceID = "com.amos.server";
+ private String clientID;
+ private String clientName;
+
+ private NearbyService nearbyService;
+
+ /**
+ * Create new nearby server with the given name.
+ *
+ * @param service
+ */
+ public NearbyServer(@NonNull NearbyService service) throws SecurityException {
+ this.nearbyService = service;
+ this.connectionsClient = Nearby.getConnectionsClient(this.nearbyService);
+ }
+
+ /**
+ * Server name as combination of name with suffix which is our nearby code
+ * @return
+ */
+ public String getServerName() {
+ return serviceName + this.nearbyService.getNearbyCode();
+ }
+
+ /**
+ * Obtain data from clientID/clientName and data transfer information via this handle.
+ */
+ private final PayloadCallback payloadCallback =
+ new PayloadCallback() {
+ @Override
+ public void onPayloadReceived(String endpointId, Payload payload) {
+ nearbyService.handlePayload(payload);
+ }
+
+ @Override
+ public void onPayloadTransferUpdate(String endpointId, PayloadTransferUpdate update) {
+ nearbyService.handlePayloadTransferUpdate(update);
+ }
+ };
+
+ /**
+ * Broadcast our presence using Nearby Connection so FlyInn users can find us.
+ * Resets clientID and clientName first.
+ */
+ public void start() {
+ clearClientData();
+
+ AdvertisingOptions advertisingOptions =
+ new AdvertisingOptions.Builder().setStrategy(STRATEGY).build();
+
+ connectionsClient.startAdvertising(getServerName(), serviceID,
+ connectionLifecycleCallback, advertisingOptions)
+ .addOnSuccessListener((Void unused) -> {
+ Log.d(TAG, "Start advertising Android nearby");
+ nearbyService.setServiceState(NearbyState.ADVERTISING, "Advertising android nearby");
+ })
+ .addOnFailureListener((Exception e) -> {
+ Log.d(TAG, "Error trying to advertise Android nearby");
+ nearbyService.handleResponse(true, e.toString());
+ nearbyService.setServiceState(NearbyState.STOPPED, "Failed to advertise android nearby");
+ });
+ }
+
+ /**
+ * Stop all things nearby
+ */
+ public void stop() {
+ connectionsClient.stopAllEndpoints();
+ Log.d(TAG, "Stopped all endpoints");
+ nearbyService.setServiceState(NearbyState.STOPPED, "Stopped all endpoints");
+ }
+
+ /**
+ * Callbacks for connections to other devices.
+ * Includes token authentication and connection handling.
+ */
+ private final ConnectionLifecycleCallback connectionLifecycleCallback =
+ new ConnectionLifecycleCallback() {
+ @Override
+ public void onConnectionInitiated(String endpointId, ConnectionInfo connectionInfo) {
+ clientName = connectionInfo.getEndpointName();
+ connectionsClient.acceptConnection(endpointId, payloadCallback);
+ Log.i(TAG, "Auto accepting initiated connection from " + endpointId);
+ nearbyService.setServiceState(NearbyState.CONNECTING, "Connecting to " + endpointId);
+ }
+
+ @Override
+ public void onConnectionResult(String endpointId, ConnectionResolution result) {
+ switch (result.getStatus().getStatusCode()) {
+ case ConnectionsStatusCodes.STATUS_OK:
+ // successful connection with client
+ Log.i(TAG, "Connected with " + endpointId);
+ connectionsClient.stopAdvertising();
+ clientID = endpointId;
+ nearbyService.setServiceState(NearbyState.CONNECTED, "Connected to " + endpointId);
+ break;
+
+ case ConnectionsStatusCodes.STATUS_CONNECTION_REJECTED:
+ // connection was rejected by one side (or both)
+ Log.i(TAG, "Connection rejected with " + endpointId);
+ nearbyService.setServiceState(NearbyState.ADVERTISING, "Rejected with " + endpointId);
+ clearClientData();
+ break;
+
+ case ConnectionsStatusCodes.STATUS_ERROR:
+ // connection was lost
+ Log.w(TAG, "Connection lost: " + endpointId);
+ nearbyService.setServiceState(NearbyState.ADVERTISING, "Connection lost to " + endpointId);
+ clearClientData();
+ break;
+
+ default:
+ // unknown status code. we shouldn't be here
+ Log.e(TAG, "Unknown error when attempting to connect with " + endpointId);
+ nearbyService.setServiceState(NearbyState.ADVERTISING, "Unknown error with " + endpointId);
+ clearClientData();
+ }
+ }
+
+ @Override
+ public void onDisconnected(String endpointId) {
+ // disconnected from client
+ Log.i(TAG, "Disconnected from " + endpointId);
+ nearbyService.setServiceState(NearbyState.ADVERTISING, "Disconnected from " + endpointId);
+ }
+ };
+
+ /**
+ * Resets client ID and client name to null.
+ */
+ private void clearClientData() {
+ clientID = null;
+ clientName = null;
+ }
+}
diff --git a/app/src/main/java/com/amos/flyinn/nearbyservice/NearbyService.java b/app/src/main/java/com/amos/flyinn/nearbyservice/NearbyService.java
new file mode 100644
index 00000000..53e041c9
--- /dev/null
+++ b/app/src/main/java/com/amos/flyinn/nearbyservice/NearbyService.java
@@ -0,0 +1,293 @@
+package com.amos.flyinn.nearbyservice;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.IntentService;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.TaskStackBuilder;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Handler;
+import android.support.annotation.Nullable;
+import android.support.v4.app.NotificationCompat;
+import android.util.Log;
+
+
+import com.amos.flyinn.ConnectionSetupActivity;
+import com.amos.flyinn.R;
+import com.amos.flyinn.ShowCodeActivity;
+import com.google.android.gms.nearby.connection.Payload;
+import com.google.android.gms.nearby.connection.PayloadTransferUpdate;
+
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * Manage nearby connections with a server.
+ *
+ * After starting this service. This will actively advertise
+ * the current device for nearby connections. A server can then
+ * initiate a connection over nearby.
+ */
+public class NearbyService extends IntentService {
+
+ public static final String TAG = NearbyService.class.getPackage().getName();
+
+ public static final String ACTION_START = "nearby_start";
+ public static final String ACTION_STOP = "nearby_stop";
+
+ private NearbyServer server;
+
+ private static final int FOREGROUND_ID = 1;
+ private static final int NOTIFY_ID = 2;
+ private static final String CHANNEL_ID = "flyinn_nearby";
+
+ private String nearbyCode = "";
+
+ /**
+ * Define the serviceState of our service.
+ */
+ private NearbyState serviceState = NearbyState.STOPPED;
+
+ public NearbyService() {
+ super("NearbyService");
+ }
+
+ public static String[] getRequiredPermissions() {
+ return NearbyServer.REQUIRED_PERMISSIONS;
+ }
+
+ @Override
+ public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
+ Log.d(TAG, "Creating channel");
+ createChannel();
+ return super.onStartCommand(intent, flags, startId);
+ }
+
+ /**
+ * Create a notification channel.
+ *
+ * Notifications are organized into different channels to theoretically enable the user to
+ * individually set what they want to be informed about.
+ *
+ * This is required since Android 8.
+ */
+ private void createChannel() {
+ NotificationManager mgr=
+ (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
+ if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.O &&
+ mgr.getNotificationChannel(CHANNEL_ID)==null) {
+
+ NotificationChannel c=new NotificationChannel(CHANNEL_ID,
+ "flyinn_channel", NotificationManager.IMPORTANCE_HIGH);
+
+ c.enableLights(true);
+ c.setLightColor(0xFFFF0000);
+
+ mgr.createNotificationChannel(c);
+ }
+ }
+
+ /**
+ * Create a sticky notification that won't go away.
+ * @param message String message shown in the notification.
+ * @param target Optional target intent to switch to after tapping the notification.
+ * @return
+ */
+ private Notification buildForegroundNotification(String message, @Nullable Intent target) {
+ NotificationCompat.Builder b =
+ new NotificationCompat.Builder(this, CHANNEL_ID);
+
+ b.setOngoing(true)
+ .setPriority(NotificationCompat.PRIORITY_HIGH) // makes notification pop up
+ .setContentTitle(String.format("Nearby service %s", nearbyCode))
+ .setContentText(message)
+ .setSmallIcon(android.R.drawable.stat_notify_sync);
+
+ if (target != null) {
+ TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+ stackBuilder.addNextIntentWithParentStack(target);
+ PendingIntent pendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
+ b.setContentIntent(pendingIntent);
+ }
+
+ return (b.build());
+ }
+
+ private void raiseNotification(Notification notification) {
+ NotificationManager mgr = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ startForeground(FOREGROUND_ID, buildForegroundNotification("Nearby Action running", null));
+ mgr.notify(NOTIFY_ID, notification);
+ }
+
+ /**
+ * Create or update shown notification.
+ * @param message
+ */
+ public void notify(String message) {
+ Intent intent = null;
+ switch (serviceState) {
+ case STOPPED:
+ intent = new Intent(this, ShowCodeActivity.class);
+ intent.putExtra("code", getNearbyCode());
+ break;
+ case ADVERTISING:
+ intent = new Intent(this, ShowCodeActivity.class);
+ intent.putExtra("code", getNearbyCode());
+ break;
+ case CONNECTING:
+ intent = new Intent(this, ConnectionSetupActivity.class);
+ intent.putExtra("code", getNearbyCode());
+ break;
+ case CONNECTED:
+ intent = new Intent(this, ConnectionSetupActivity.class);
+ intent.putExtra("code", getNearbyCode());
+ break;
+ }
+ raiseNotification(buildForegroundNotification(message, intent));
+ }
+
+ /**
+ * Activate the given activity
+ * @param cls Class of the target activity
+ */
+ private void switchActivity(Class> cls) {
+ Intent intent = new Intent(this, cls);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ }
+
+ public NearbyState getServiceState() {
+ return serviceState;
+ }
+
+ /**
+ * Set state of the nearby service. This is used by the nearby server.
+ * @param state
+ * @param message
+ */
+ public void setServiceState(NearbyState state, @Nullable String message) {
+ // do extra things if we are switching state
+ if (serviceState != state) {
+ switch(serviceState) {
+ case CONNECTING:
+ switchActivity(ConnectionSetupActivity.class);
+ break;
+ }
+ serviceState = state;
+ }
+ if (message != null) {
+ notify(message);
+ }
+ }
+
+ /**
+ * Create an Intent to control NearbyService.
+ *
+ * @param state Defined serviceState for the application as enum.
+ * @param context
+ * @return Intent containing desired application serviceState.
+ */
+ public static Intent createNearbyIntent(String state, Context context) {
+ Intent intent = new Intent(context, NearbyService.class);
+ intent.setAction(state);
+ return intent;
+ }
+
+ public void handleResponse(boolean error, String message) {
+ if (error) {
+ Log.d(TAG, String.format("Error received: %s", message));
+ } else {
+ Log.d(TAG, String.format("Status update: %s", message));
+ }
+ }
+
+ public void handlePayload(Payload payload) {
+ Log.d(TAG, "Received payload");
+ }
+
+ public void handlePayloadTransferUpdate(PayloadTransferUpdate update) {
+ Log.d(TAG, "Received transfer update");
+ }
+
+ /**
+ * Start advertising Android nearby.
+ */
+ public void start() {
+ if (serviceState == NearbyState.STOPPED) {
+ Log.d(TAG, "Starting NearbyService");
+ if (server == null) {
+ try {
+ server = new NearbyServer(this);
+ server.start();
+ } catch (SecurityException error) {
+ notify("Insufficient permissions");
+ }
+ }
+ } else {
+ Log.d(TAG, "NearbyService already started");
+ }
+ }
+
+ /**
+ * Stop advertising Android nearby.
+ */
+ public void stop() {
+ if (serviceState != NearbyState.STOPPED) {
+ Log.d(TAG, "Stopping NearbyService");
+ serviceState = NearbyState.STOPPED;
+ notify("Stopping nearby advertising");
+ server.stop();
+ } else {
+ Log.d(TAG, "NearbyService already stopped");
+ }
+ }
+
+ /**
+ * Handle intents for initiation of advertising and connection shutdown.
+ *
+ * @param intent An intent with a custom Extra field called action.
+ */
+ @Override
+ protected void onHandleIntent(@Nullable Intent intent) {
+ Log.d(TAG, "Handling intent now");
+ if (intent.hasExtra("code")) {
+ try {
+ String code = intent.getStringExtra("code");
+ setNearbyCode(code);
+ Log.d(TAG, String.format("Setting code to %s", code));
+ } catch (NullPointerException err) {
+ Log.d(TAG, "Could not get code from intent.");
+ }
+ }
+
+ try {
+ switch (intent.getAction()) {
+ case ACTION_START:
+ start();
+ break;
+ case ACTION_STOP:
+ stop();
+ break;
+ default:
+ Log.d(TAG, "Unknown intent received. Will do nothing");
+ break;
+ }
+ } catch (NullPointerException error) {
+ Log.d(TAG, "Null pointer for action received. Will do nothing");
+ }
+ }
+
+ public void setNearbyCode(String code) {
+ nearbyCode = code;
+ }
+
+ public String getNearbyCode() {
+ return nearbyCode;
+ }
+}
diff --git a/app/src/main/java/com/amos/flyinn/nearbyservice/NearbyState.java b/app/src/main/java/com/amos/flyinn/nearbyservice/NearbyState.java
new file mode 100644
index 00000000..aabca6a1
--- /dev/null
+++ b/app/src/main/java/com/amos/flyinn/nearbyservice/NearbyState.java
@@ -0,0 +1,14 @@
+package com.amos.flyinn.nearbyservice;
+
+/**
+ * Define possible states of the Nearby service. These are set by the service itself or by the
+ * server.
+ */
+public enum NearbyState {
+ // Default state or state of errors
+ UNKNOWN,
+ STOPPED,
+ ADVERTISING,
+ CONNECTING,
+ CONNECTED,
+}
diff --git a/app/src/main/res/raw/fakeinputlib.jar b/app/src/main/res/raw/fakeinputlib.jar
deleted file mode 100644
index 7d57effc..00000000
Binary files a/app/src/main/res/raw/fakeinputlib.jar and /dev/null differ
diff --git a/app/src/test/java/com/amos/flyinn/nearbyservice/NearbyServerTest.java b/app/src/test/java/com/amos/flyinn/nearbyservice/NearbyServerTest.java
new file mode 100644
index 00000000..2e09eee7
--- /dev/null
+++ b/app/src/test/java/com/amos/flyinn/nearbyservice/NearbyServerTest.java
@@ -0,0 +1,17 @@
+package com.amos.flyinn.nearbyservice;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/**
+ * See inside NearbyService for tests including NearbyServer
+ */
+@RunWith(RobolectricTestRunner.class)
+public class NearbyServerTest {
+
+ @Test(expected = NullPointerException.class)
+ public void init_null() {
+ NearbyServer server = new NearbyServer(null);
+ }
+}
diff --git a/app/src/test/java/com/amos/flyinn/nearbyservice/NearbyServiceTest.java b/app/src/test/java/com/amos/flyinn/nearbyservice/NearbyServiceTest.java
new file mode 100644
index 00000000..b8eaee34
--- /dev/null
+++ b/app/src/test/java/com/amos/flyinn/nearbyservice/NearbyServiceTest.java
@@ -0,0 +1,139 @@
+package com.amos.flyinn.nearbyservice;
+
+import android.content.Intent;
+
+import org.junit.After;
+
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.android.controller.ServiceController;
+
+import androidx.annotation.Nullable;
+import androidx.test.core.app.ApplicationProvider;
+
+/**
+ * Wrap Nearby service in a way to test its functions individually
+ * Source: https://stackoverflow.com/a/33755494
+ */
+@RunWith(RobolectricTestRunner.class)
+public class NearbyServiceTest {
+ private NearbyService service;
+ private ServiceController controller;
+
+ @Test
+ public void testBasicIntent() {
+ Intent intent = new Intent(ApplicationProvider.getApplicationContext(), TestService.class);
+ controller = Robolectric.buildService(TestService.class, intent);
+ service = controller.create().get();
+ }
+
+ /**
+ * Check that passing code with intent works correctly.
+ */
+ @Test
+ public void testCodeIntent() {
+ Intent intent = new Intent(ApplicationProvider.getApplicationContext(), TestService.class);
+ intent.putExtra("code", "1234");
+ intent.setAction("");
+ controller = Robolectric.buildService(TestService.class, intent);
+ service = controller.create().get();
+ controller.startCommand(0, 0);
+ assertEquals("1234", service.getNearbyCode());
+ }
+
+ /**
+ * Check that leaving action to be null will also work correctly.
+ */
+ @Test
+ public void testNullActionIntent() {
+ Intent intent = new Intent(ApplicationProvider.getApplicationContext(), TestService.class);
+ intent.putExtra("code", "1234");
+ controller = Robolectric.buildService(TestService.class, intent);
+ service = controller.create().get();
+ controller.startCommand(0, 0);
+ assertEquals("1234", service.getNearbyCode());
+ }
+
+ @Test
+ public void testStartIntent() {
+ Intent intent = new Intent(ApplicationProvider.getApplicationContext(), TestService.class);
+ intent.putExtra("code", "1234");
+ intent.setAction(NearbyService.ACTION_START);
+ controller = Robolectric.buildService(TestService.class, intent);
+ service = controller.create().get();
+ controller.startCommand(0, 0);
+ }
+
+ @Test
+ public void testStopIntent() {
+ Intent intent = new Intent(ApplicationProvider.getApplicationContext(), TestService.class);
+ intent.putExtra("code", "1234");
+ intent.setAction(NearbyService.ACTION_STOP);
+ controller = Robolectric.buildService(TestService.class, intent);
+ service = controller.create().get();
+ controller.startCommand(0, 0);
+ }
+
+ @Test
+ public void testSetNearbyCode() {
+ Intent intent = new Intent(ApplicationProvider.getApplicationContext(), TestService.class);
+ intent.putExtra("code", "1234");
+ intent.setAction("");
+ controller = Robolectric.buildService(TestService.class, intent);
+ service = controller.create().get();
+ controller.startCommand(0, 0);
+ assertEquals("1234", service.getNearbyCode());
+ service.setNearbyCode("5678");
+ assertEquals("5678", service.getNearbyCode());
+ }
+
+ @Test
+ public void testSetState() {
+ Intent intent = new Intent(ApplicationProvider.getApplicationContext(), TestService.class);
+ intent.putExtra("code", "1234");
+ intent.setAction("");
+ controller = Robolectric.buildService(TestService.class, intent);
+ service = controller.create().get();
+ controller.startCommand(0, 0);
+ service.setServiceState(NearbyState.STOPPED, "Test");
+ assertEquals(NearbyState.STOPPED, service.getServiceState());
+ service.setServiceState(NearbyState.CONNECTED, "Test");
+ assertEquals(NearbyState.CONNECTED, service.getServiceState());
+ service.setServiceState(NearbyState.CONNECTING, "Test");
+ assertEquals(NearbyState.CONNECTING, service.getServiceState());
+ }
+
+ @Test
+ public void testNotify() {
+ Intent intent = new Intent(ApplicationProvider.getApplicationContext(), TestService.class);
+ intent.putExtra("code", "1234");
+ intent.setAction("");
+ controller = Robolectric.buildService(TestService.class, intent);
+ service = controller.create().get();
+ service.notify("test");
+ }
+
+ @Test
+ public void testCreateNearbyIntent() {
+ Intent intent = NearbyService.createNearbyIntent(NearbyService.ACTION_START, ApplicationProvider.getApplicationContext());
+ assertEquals(NearbyService.ACTION_START, intent.getAction());
+ controller = Robolectric.buildService(TestService.class, intent);
+ service = controller.create().get();
+ }
+
+ @After
+ public void tearDown() {
+ controller.destroy();
+ }
+
+ public static class TestService extends NearbyService {
+ @Override
+ public void onStart(@Nullable Intent intent, int startId) {
+ onHandleIntent(intent);
+ stopSelf(startId);
+ }
+ }
+}
diff --git a/server/src/main/AndroidManifest.xml b/server/src/main/AndroidManifest.xml
index d41ced51..e1449139 100644
--- a/server/src/main/AndroidManifest.xml
+++ b/server/src/main/AndroidManifest.xml
@@ -43,19 +43,17 @@
android:name=".BuildInfoActivity"
android:label="@string/title_activity_build_info"
android:theme="@style/AppTheme.NoActionBar" />
-
-
+
+
+
+
+
+
-
-
-
-
diff --git a/server/src/main/java/com/amos/server/ConnectToClientActivity.java b/server/src/main/java/com/amos/server/ConnectToClientActivity.java
index f19a3aba..cf2230d2 100644
--- a/server/src/main/java/com/amos/server/ConnectToClientActivity.java
+++ b/server/src/main/java/com/amos/server/ConnectToClientActivity.java
@@ -1,14 +1,240 @@
package com.amos.server;
+import android.Manifest;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Build;
import android.os.Bundle;
+import android.support.annotation.CallSuper;
+import android.support.annotation.NonNull;
+import android.support.v4.content.ContextCompat;
+import android.util.Log;
import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.TextView;
+import android.widget.Toast;
-import com.amos.server.wifibroadcaster.WifiHijackBase;
+import com.google.android.gms.nearby.Nearby;
+import com.google.android.gms.nearby.connection.ConnectionInfo;
+import com.google.android.gms.nearby.connection.ConnectionLifecycleCallback;
+import com.google.android.gms.nearby.connection.ConnectionResolution;
+import com.google.android.gms.nearby.connection.ConnectionsClient;
+import com.google.android.gms.nearby.connection.ConnectionsStatusCodes;
+import com.google.android.gms.nearby.connection.DiscoveredEndpointInfo;
+import com.google.android.gms.nearby.connection.DiscoveryOptions;
+import com.google.android.gms.nearby.connection.EndpointDiscoveryCallback;
+import com.google.android.gms.nearby.connection.Payload;
+import com.google.android.gms.nearby.connection.PayloadCallback;
+import com.google.android.gms.nearby.connection.PayloadTransferUpdate;
+import com.google.android.gms.nearby.connection.Strategy;
+
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class ConnectToClientActivity extends Activity {
+
+ private static final String[] REQUIRED_PERMISSIONS =
+ new String[] {
+ Manifest.permission.BLUETOOTH,
+ Manifest.permission.BLUETOOTH_ADMIN,
+ Manifest.permission.ACCESS_WIFI_STATE,
+ Manifest.permission.CHANGE_WIFI_STATE,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ };
+
+ private static final int REQUEST_CODE_REQUIRED_PERMISSIONS = 1;
+
+ /** 1-to-1 since a device will be connected to only one other device at most. */
+ private static final Strategy STRATEGY = Strategy.P2P_POINT_TO_POINT;
+
+ /** Connection manager for the connection to FlyInn clients. */
+ protected ConnectionsClient connectionsClient;
+
+ private final String clientName = generateName(5);
+ private String clientID;
+ private String clientNameNow;
+
+ /** Toast to publish user notifications */
+ private Toast mToast;
+
+ /** List of all discovered servers by name, continuously updated. */
+ private List clients = new ArrayList<>();
+
+ /** Maps server names to their nearby connection IDs. */
+ private HashMap clientNamesToIDs = new HashMap<>();
+
+ /** Maps server IDs to their nearby connection names. */
+ private HashMap clientIDsToNames = new HashMap<>();
+
+ /** Tag for logging purposes. */
+ private static final String TAG = "ClientNearbyConnection";
+
+
+
+
+ /**
+ * Obtain data from clientID/clientName and data transfer information via this handle.
+ */
+ private final PayloadCallback payloadCallback =
+ new PayloadCallback() {
+ @Override
+ public void onPayloadReceived(String endpointId, Payload payload) {
+ Log.d(TAG, "Payload received");
+ }
+
+ @Override
+ public void onPayloadTransferUpdate(String endpointId, PayloadTransferUpdate update) {
+ Log.d(TAG, "Payload transfer update");
+ }
+ };
+
+
+ /**
+ * Handling of discovered endpoints (servers). Adds new endpoints to servers data maps/list,
+ * and removes lost endpoints.
+ */
+ private final EndpointDiscoveryCallback endpointDiscoveryCallback =
+ new EndpointDiscoveryCallback() {
+ @Override
+ public void onEndpointFound(String endpointId, DiscoveredEndpointInfo info) {
+ // discovered a server, add to data maps
+ String endpointName = info.getEndpointName();
+
+ if (!(clientIDsToNames.containsKey(endpointId)
+ || clientNamesToIDs.containsKey(endpointName))) {
+ clients.add(endpointName);
+ clientNamesToIDs.put(endpointName, endpointId);
+ clientIDsToNames.put(endpointId, endpointName);
+ Log.i(TAG, clientName + " discovered endpoint " + endpointId + " with name " + endpointName);
+
+ } else {
+ // this should not happen
+ while (clients.remove(endpointName)) {}
+ clients.add(endpointName);
+ clientIDsToNames.put(endpointId, endpointName);
+ clientNamesToIDs.put(endpointName, endpointId);
+ Log.i(TAG, clientName + " rediscovered endpoint " + endpointId + " with name " + endpointName);
+ }
+ }
+
+ @Override
+ public void onEndpointLost(String endpointId) {
+ // previously discovered server is no longer reachable, remove from data maps
+ String lostEndpointName = clientIDsToNames.get(endpointId);
+ clientIDsToNames.remove(endpointId);
+ clientNamesToIDs.remove(lostEndpointName);
+ Log.i(TAG, clientName + " lost discovered endpoint " + endpointId);
+ }
+ };
+
+
+ /**
+ * Callbacks for connections to other devices.
+ * Includes token authentication and connection handling.
+ */
+ private final ConnectionLifecycleCallback connectionLifecycleCallback =
+ new ConnectionLifecycleCallback() {
+ @Override
+ public void onConnectionInitiated(String endpointId, ConnectionInfo connectionInfo) {
+ Log.i(TAG, "Connection initiated to " + endpointId);
+
+ if (endpointId.equals(clientID)) {
+ connectionsClient.acceptConnection(endpointId, payloadCallback);
+ } else {
+ // initiated connection is not with server selected by user
+ connectionsClient.rejectConnection(endpointId);
+ Log.i(TAG, "Connection rejected to non-selected server "
+ + endpointId);
+ }
+ }
+
+ @Override
+ public void onConnectionResult(String endpointId, ConnectionResolution result) {
+ switch (result.getStatus().getStatusCode()) {
+
+ case ConnectionsStatusCodes.STATUS_OK:
+ // successful connection with server
+ Log.i(TAG, "Connected with " + endpointId);
+ mToast.setText(R.string.nearby_connection_success);
+ mToast.show();
+ connectedToApp();
+ break;
+
+ case ConnectionsStatusCodes.STATUS_CONNECTION_REJECTED:
+ // connection was rejected by one side (or both)
+ Log.i(TAG, "Connection rejected with " + endpointId);
+ mToast.setText(R.string.nearby_connection_rejected);
+ mToast.show();
+ clientNameNow = null;
+ clientID = null;
+ break;
+
+ case ConnectionsStatusCodes.STATUS_ERROR:
+ // connection was lost
+ Log.w(TAG, "Connection lost: " + endpointId);
+ mToast.setText(R.string.nearby_connection_error);
+ mToast.show();
+ clientNameNow = null;
+ clientID = null;
+ break;
+
+ default:
+ // unknown status code. we shouldn't be here
+ Log.e(TAG, "Unknown error when attempting to connect with "
+ + endpointId);
+ mToast.setText(R.string.nearby_connection_error);
+ mToast.show();
+ clientNameNow = null;
+ clientID = null;
+ }
+ }
+
+ @Override
+ public void onDisconnected(String endpointId) {
+ // disconnected from server
+ Log.i(TAG, "Disconnected from " + endpointId);
+ mToast.setText(R.string.nearby_disconnected);
+ mToast.show();
+ clearServerData();
+ finish();
+ }
+ };
+
+
+
+ private void requestConnectionWithClient(String code){
+
+ String searchedClientName = null;
+ for (String clientName : clients){
+ if(clientName.endsWith(code))
+ {
+ searchedClientName = clientName;
+ break;
+ }
+ }
+
+ if (searchedClientName == null)
+ {
+ Toast.makeText(this,"The code given was not found. Please try again",Toast.LENGTH_LONG).show();
+ return;
+ }
+
+
+ String endpoint = clientNamesToIDs.get(searchedClientName);
+ clientID = endpoint;
+
+
+ connectionsClient.requestConnection(searchedClientName,endpoint,connectionLifecycleCallback);
+
+
+
+ }
-public class ConnectToClientActivity extends WifiHijackBase {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -19,7 +245,7 @@ protected void onCreate(Bundle savedInstanceState) {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
String name = v.getText().toString(); // Get the String
- onNameEnterFinished(name);
+ requestConnectionWithClient(name);
return true;
}
return false;
@@ -27,14 +253,165 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
});
+ if (!hasPermissions(this, REQUIRED_PERMISSIONS) &&
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ requestPermissions(REQUIRED_PERMISSIONS, REQUEST_CODE_REQUIRED_PERMISSIONS);
+ } else {
+ Log.w(TAG, "Could not check permissions due to version");
+ }
+
+ connectionsClient = Nearby.getConnectionsClient(this);
+ mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
+
+
+ startDiscovering();
+
+
+ }
+
+ /**
+ * Checks needed permissions for nearby connection
+ */
+ @Override
+ protected void onStart() {
+ super.onStart();
+
+ // user may have changed permissions
+ if (!hasPermissions(this, REQUIRED_PERMISSIONS) &&
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ requestPermissions(REQUIRED_PERMISSIONS, REQUEST_CODE_REQUIRED_PERMISSIONS);
+ } else {
+ Log.w(TAG, "Could not check permissions due to version");
+ }
+ }
+
+ /**
+ * Stops all connection discovery and connections from this client
+ * before calling super.onDestroy()
+ */
+ @Override
+ protected void onDestroy() {
+ connectionsClient.stopDiscovery();
+ connectionsClient.stopAllEndpoints();
+ clearServerData();
+ super.onDestroy();
+ }
+
+ /**
+ * Clears all servers map data as well as serverName/serverID and starts discovery
+ */
+ private void startDiscovering() {
+ clearServerData();
+
+ DiscoveryOptions discoveryOptions =
+ new DiscoveryOptions.Builder().setStrategy(STRATEGY).build();
+
+ connectionsClient.startDiscovery("com.amos.server", endpointDiscoveryCallback,
+ discoveryOptions)
+ .addOnSuccessListener( (Void unused) -> {
+ // started searching for servers successfully
+ Log.i(TAG, "Discovering connections on " + clientName);
+ mToast.setText(R.string.nearby_discovering_success);
+ mToast.show();
+ })
+ .addOnFailureListener( (Exception e) -> {
+ // unable to start discovery
+ Log.e(TAG, "Unable to start discovery on " + clientName);
+ mToast.setText(R.string.nearby_discovering_error);
+ mToast.show();
+ finish();
+ });
+ }
+
+ /**
+ * Clears serverName/serverID and all server data maps as well as the servers list
+ */
+ private void clearServerData() {
+ clients.clear();
+ clientIDsToNames.clear();
+ clientNamesToIDs.clear();
+ clientID = null;
+ clientNameNow = null;
+ }
+
+ /**
+ * Handle established connection with app.
+ *
+ * Clears servers data maps, stops discovery of new servers and adds close connection button
+ */
+ private void connectedToApp() {
+ connectionsClient.stopDiscovery();
+ clients.clear();
+ clientNamesToIDs.clear();
+ clientIDsToNames.clear();
+
+ // Start setting up a connection with the other side
+ Intent intent = new Intent(this, ConnectionSetupServerActivity.class);
+ intent.putExtra("endpointId", clientID);
+ startActivity(intent);
+ }
+
+ /**
+ * Determines whether the FlyInn server app has the necessary permissions to run nearby.
+ * @param context Checks the permissions against this context/application environment
+ * @param permissions The permissions to be checked
+ * @return True if the app was granted all the permissions, false otherwise
+ */
+ private static boolean hasPermissions(Context context, String[] permissions) {
+ for (String permission : permissions) {
+ if (ContextCompat.checkSelfPermission(context, permission)
+ != PackageManager.PERMISSION_GRANTED) {
+ return false;
+ }
+ }
+ return true;
}
+ /**
+ * Handles user acceptance (or denial) of our permission request.
+ * @param requestCode The request code passed in requestPermissions()
+ * @param permissions Permissions that must be granted to run nearby connections
+ * @param grantResults Results of granting permissions
+ */
+ @CallSuper
+ @Override
+ public void onRequestPermissionsResult(
+ int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ if (requestCode != REQUEST_CODE_REQUIRED_PERMISSIONS) {
+ return;
+ }
- private void onNameEnterFinished(String name) {
- this.changeName(name);
+ for (int grantResult : grantResults) {
+ if (grantResult == PackageManager.PERMISSION_DENIED) {
+ Log.w(TAG, "Permissions necessary for " +
+ "Nearby Connection were not granted.");
+ mToast.setText(R.string.nearby_missing_permissions);
+ mToast.show();
+ finish();
+ }
+ }
+ recreate();
}
+ /**
+ * Generates a name for the server.
+ * @return The server name, consisting of the build model + a random string
+ */
+ // TODO Define better name system?
+ private String generateName(int appendixLength){
+ String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ SecureRandom rnd = new SecureRandom();
+ StringBuilder sb = new StringBuilder(appendixLength);
+ for (int i = 0; i < appendixLength; i++) {
+ sb.append(AB.charAt(rnd.nextInt(AB.length())));
+ }
+
+ String name = Build.MODEL + "_" + sb.toString();
+ Log.i(TAG, "Current name is: " + name);
+ return name;
+ }
}
diff --git a/server/src/main/java/com/amos/server/ConnectionSetupServerActivity.java b/server/src/main/java/com/amos/server/ConnectionSetupServerActivity.java
index 8668dbe3..4efadd7c 100644
--- a/server/src/main/java/com/amos/server/ConnectionSetupServerActivity.java
+++ b/server/src/main/java/com/amos/server/ConnectionSetupServerActivity.java
@@ -7,10 +7,8 @@
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
import android.support.annotation.Nullable;
-import android.view.MotionEvent;
+import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
@@ -21,11 +19,13 @@
import com.amos.server.webrtc.PeerWrapper;
import com.amos.server.webrtc.SetupStates;
import com.amos.shared.TouchEvent;
+import com.google.android.gms.nearby.Nearby;
+import com.google.android.gms.nearby.connection.ConnectionsClient;
+import com.google.android.gms.nearby.connection.Payload;
import org.webrtc.SurfaceViewRenderer;
import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
public class ConnectionSetupServerActivity extends Activity {
@@ -44,6 +44,10 @@ public class ConnectionSetupServerActivity extends Activity {
private PeerWrapper peerWrapper;
private SurfaceViewRenderer remoteRender;
+ private String endpointId;
+
+ private static final String TAG = "ConnectionSetup";
+
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -54,39 +58,21 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
connectionInfo = findViewById(R.id.connectionInfo);
connectionInfo.setVisibility(View.INVISIBLE);
- //setStateText(SetupStates.LOCAL_DESCRIPTOR_CREATE);
- // create touch listener components
- msgQueue = new LinkedBlockingQueue<>();
- uiHandler = new Handler(Looper.getMainLooper()) {
- @Override
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- connectionInfo.setText((String) msg.obj);
+ Intent intent = getIntent();
+ if (intent.hasExtra("endpointId")) {
+ try {
+ endpointId = intent.getStringExtra("endpointId");
+ ConnectionsClient connection = Nearby.getConnectionsClient(this);
+ byte[] message = {0x61, 0x61, 0x61, 0x62};
+ Payload payload = Payload.fromBytes(message);
+ connection.sendPayload(endpointId, payload);
+ Log.d(TAG, "Sent test payload to receiver " + endpointId);
+ } catch (NullPointerException error) {
+ Log.d(TAG, "Failed to get endpointId from intent");
}
- };
- eventSender = new EventServer(msgQueue, uiHandler);
-
-
- //init WebRTC Signaling server
- this.initViews();
- this.peerWrapper = new PeerWrapper(this);
- this.webSocketServer = new WebServer(this.peerWrapper);
- this.peerWrapper.setEmitter(this.webSocketServer);
- this.webSocketServer.start();
-
-
- senderRunner = new Thread(eventSender);
- senderRunner.start();
- view.setOnTouchListener(
- (View v, MotionEvent e) -> {
- e.setLocation(e.getX() / view.getWidth(), e.getY() / view.getHeight());
- TouchEvent te = new TouchEvent(e.getX(), e.getY(), e.getAction(), e.getDownTime());
- msgQueue.add(te);
- return true;
- }
- );
-
-
+ } else {
+ Log.d(TAG, "Intent did not specify endpoint");
+ }
}
/**
@@ -104,7 +90,7 @@ private void initViews() {
}
- private void restarAPP() {
+ private void restartApp() {
Intent i = getBaseContext().getPackageManager()
.getLaunchIntentForPackage(getBaseContext().getPackageName());
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -135,7 +121,7 @@ public void setStateText(int state) {
builder.setNegativeButton("Restart app", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
- restarAPP();
+ restartApp();
}
});
diff --git a/server/src/main/java/com/amos/server/NearbyServerActivity.java b/server/src/main/java/com/amos/server/NearbyServerActivity.java
deleted file mode 100644
index 9e840175..00000000
--- a/server/src/main/java/com/amos/server/NearbyServerActivity.java
+++ /dev/null
@@ -1,318 +0,0 @@
-package com.amos.server;
-
-import android.Manifest;
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.pm.PackageManager;
-import android.os.Build;
-import android.os.Bundle;
-import android.app.Activity;
-import android.os.Handler;
-import android.support.annotation.CallSuper;
-import android.support.annotation.NonNull;
-import android.support.v4.content.ContextCompat;
-import android.util.Log;
-import android.widget.Toast;
-
-import com.google.android.gms.nearby.Nearby;
-import com.google.android.gms.nearby.connection.AdvertisingOptions;
-import com.google.android.gms.nearby.connection.ConnectionInfo;
-import com.google.android.gms.nearby.connection.ConnectionLifecycleCallback;
-import com.google.android.gms.nearby.connection.ConnectionResolution;
-import com.google.android.gms.nearby.connection.ConnectionsClient;
-import com.google.android.gms.nearby.connection.ConnectionsStatusCodes;
-import com.google.android.gms.nearby.connection.Payload;
-import com.google.android.gms.nearby.connection.PayloadCallback;
-import com.google.android.gms.nearby.connection.PayloadTransferUpdate;
-import com.google.android.gms.nearby.connection.Strategy;
-
-import java.security.SecureRandom;
-
-/**
- * Activity that handles connection to clients via nearby connection.
- * Includes permission handling and simple token authentication.
- */
-public class NearbyServerActivity extends Activity {
-
- /** Permissions required for Nearby Connection */
- private static final String[] REQUIRED_PERMISSIONS =
- new String[] {
- Manifest.permission.BLUETOOTH,
- Manifest.permission.BLUETOOTH_ADMIN,
- Manifest.permission.ACCESS_WIFI_STATE,
- Manifest.permission.CHANGE_WIFI_STATE,
- };
-
- private static final int REQUEST_CODE_REQUIRED_PERMISSIONS = 1;
-
- /** 1-to-1 since a device will be connected to only one other device at most. */
- private static final Strategy STRATEGY = Strategy.P2P_POINT_TO_POINT;
-
- /** Connection manager for the connection to FlyInn clients.*/
- protected ConnectionsClient connectionsClient;
-
- private final String serverName = generateName(5);
- private String clientID;
- private String clientName;
-
- final Handler handler = new Handler();
-
- /** Toast to publish user notifications */
- private Toast mToast;
-
- /** Tag for logging purposes. */
- private static final String NEARBY_TAG = "ServerNearbyConnection";
-
-
- /**
- * Obtain data from clientID/clientName and data transfer information via this handle.
- */
- private final PayloadCallback payloadCallback =
- new PayloadCallback() {
- @Override
- public void onPayloadReceived(String endpointId, Payload payload) {
- // TODO
- }
-
- @Override
- public void onPayloadTransferUpdate(String endpointId, PayloadTransferUpdate update) {
- // TODO
- }
- };
-
- /**
- * Callbacks for connections to other devices.
- * Includes token authentication and connection handling.
- */
- private final ConnectionLifecycleCallback connectionLifecycleCallback =
- new ConnectionLifecycleCallback() {
- @Override
- public void onConnectionInitiated(String endpointId, ConnectionInfo connectionInfo) {
- Log.i(NEARBY_TAG, "Connection initiated by " + endpointId);
- clientName = connectionInfo.getEndpointName();
-
- // authentication via tokens
- // TODO replace token authentication with QR code/manual code input
- new AlertDialog.Builder(NearbyServerActivity.this)
- .setTitle("Accept connection to " + clientName + "?")
- .setMessage("Confirm the code matches on both devices: " +
- connectionInfo.getAuthenticationToken())
- .setPositiveButton(android.R.string.yes,
- (DialogInterface dialog, int which) ->
- // accept the connection
- connectionsClient.acceptConnection(endpointId,
- payloadCallback))
- .setNegativeButton(android.R.string.cancel,
- (DialogInterface dialog, int which) ->
- // reject the connection
- connectionsClient.rejectConnection(endpointId))
- .setIcon(android.R.drawable.ic_dialog_alert)
- .show();
- }
-
- @Override
- public void onConnectionResult(String endpointId, ConnectionResolution result) {
- switch (result.getStatus().getStatusCode()) {
-
- case ConnectionsStatusCodes.STATUS_OK:
- // successful connection with client
- Log.i(NEARBY_TAG, "Connected with " + endpointId);
- mToast.setText(R.string.nearby_connection_success);
- mToast.show();
- connectionsClient.stopAdvertising();
- clientID = endpointId;
- break;
-
- case ConnectionsStatusCodes.STATUS_CONNECTION_REJECTED:
- // connection was rejected by one side (or both)
- Log.i(NEARBY_TAG, "Connection rejected with " + endpointId);
- mToast.setText(R.string.nearby_connection_rejected);
- mToast.show();
- clearClientData();
- break;
-
- case ConnectionsStatusCodes.STATUS_ERROR:
- // connection was lost
- Log.w(NEARBY_TAG, "Connection lost: " + endpointId);
- mToast.setText(R.string.nearby_connection_error);
- mToast.show();
- clearClientData();
- break;
-
- default:
- // unknown status code. we shouldn't be here
- Log.e(NEARBY_TAG, "Unknown error when attempting to connect with "
- + endpointId);
- mToast.setText(R.string.nearby_connection_error);
- mToast.show();
- clearClientData();
- }
- }
-
- @Override
- public void onDisconnected(String endpointId) {
- // disconnected from client
- Log.i(NEARBY_TAG, "Disconnected from " + endpointId);
- mToast.setText(R.string.nearby_disconnected);
- mToast.show();
-
- // better be safe
- clearClientData();
- connectionsClient.stopAdvertising();
- connectionsClient.stopAllEndpoints();
-
- // display toast for 2s, then start advertising again
- handler.postDelayed(() -> startAdvertising(), 2000);
- }
- };
-
- /**
- * Starts a nearby connectionsClient, checks permissions and calls startAdvertising().
- * @param savedInstanceState
- */
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_nearby_server);
-
- if (!hasPermissions(this, REQUIRED_PERMISSIONS) &&
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- requestPermissions(REQUIRED_PERMISSIONS, REQUEST_CODE_REQUIRED_PERMISSIONS);
- } else {
- Log.w(NEARBY_TAG, "Could not check permissions due to version");
- }
-
- connectionsClient = Nearby.getConnectionsClient(this);
- mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
-
- startAdvertising();
- }
-
- /**
- * Checks whether the app has the required permissions to establish connections after the
- * super.onStart() call (the user may have changed permissions after starting the app).
- */
- @Override
- protected void onStart() {
- super.onStart();
-
- // user may have changed permissions
- if (!hasPermissions(this, REQUIRED_PERMISSIONS) &&
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- requestPermissions(REQUIRED_PERMISSIONS, REQUEST_CODE_REQUIRED_PERMISSIONS);
- } else {
- Log.w(NEARBY_TAG, "Could not check permissions due to version");
- }
- }
-
- /**
- * Clears client data and stops all advertising and connections from this server
- * before calling super.onDestroy().
- */
- @Override
- protected void onDestroy() {
- connectionsClient.stopAdvertising();
- connectionsClient.stopAllEndpoints();
- clearClientData();
- super.onDestroy();
- }
-
-
- /**
- * Broadcast our presence using Nearby Connection so FlyInn users can find us.
- * Resets clientID and clientName first.
- */
- private void startAdvertising() {
- clearClientData();
-
- AdvertisingOptions advertisingOptions =
- new AdvertisingOptions.Builder().setStrategy(STRATEGY).build();
-
- connectionsClient.startAdvertising(serverName, "com.amos.flyinn",
- connectionLifecycleCallback, advertisingOptions)
- .addOnSuccessListener( (Void unused) -> {
- // started advertising successfully
- Log.i(NEARBY_TAG, "Started advertising " + serverName);
- mToast.setText(R.string.nearby_advertising_success);
- mToast.show();
- })
- .addOnFailureListener( (Exception e) -> {
- // unable to advertise
- Log.e(NEARBY_TAG, "Unable to start advertising " + serverName);
- mToast.setText(R.string.nearby_advertising_error);
- mToast.show();
- finish();
- });
- }
-
- /**
- * Resets client ID and client name to null.
- */
- private void clearClientData() {
- clientID = null;
- clientName = null;
- }
-
- /**
- * Determines whether the FlyInn server app has the necessary permissions to run nearby.
- * @param context Checks the permissions against this context/application environment
- * @param permissions The permissions to be checked
- * @return True if the app was granted all the permissions, false otherwise
- */
- private static boolean hasPermissions(Context context, String[] permissions) {
- for (String permission : permissions) {
- if (ContextCompat.checkSelfPermission(context, permission)
- != PackageManager.PERMISSION_GRANTED) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Handles user acceptance (or denial) of our permission request.
- * @param requestCode The request code passed in requestPermissions()
- * @param permissions Permissions that must be granted to run nearby connections
- * @param grantResults Results of granting permissions
- */
- @CallSuper
- @Override
- public void onRequestPermissionsResult(
- int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults);
-
- if (requestCode != REQUEST_CODE_REQUIRED_PERMISSIONS) {
- return;
- }
-
- for (int grantResult : grantResults) {
- if (grantResult == PackageManager.PERMISSION_DENIED) {
- Log.w(NEARBY_TAG, "Permissions necessary for connections were not granted.");
- mToast.setText(R.string.nearby_missing_permissions);
- mToast.show();
- finish();
- }
- }
- recreate();
- }
-
- /**
- * Generates a name for the server.
- * @return The server name, consisting of the build model + a random string
- */
- // TODO Define better name system?
- private String generateName(int appendixLength){
- String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
- SecureRandom rnd = new SecureRandom();
-
- StringBuilder sb = new StringBuilder(appendixLength);
- for (int i = 0; i < appendixLength; i++) {
- sb.append(AB.charAt(rnd.nextInt(AB.length())));
- }
-
- String name = Build.MODEL + "_" + sb.toString();
- Log.i(NEARBY_TAG, "Current name is: " + name);
- return name;
- }
-}
diff --git a/server/src/main/res/layout/activity_nearby_server.xml b/server/src/main/res/layout/activity_nearby_server.xml
deleted file mode 100644
index b4b908fa..00000000
--- a/server/src/main/res/layout/activity_nearby_server.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
\ No newline at end of file