#if UNITY_ANDROID #pragma warning disable 0642 // Possible mistaken empty statement namespace GooglePlayGames.Android { using System; using System.Collections.Generic; using GooglePlayGames.BasicApi; using GooglePlayGames.BasicApi.Nearby; using GooglePlayGames.OurUtils; using UnityEngine; public class AndroidNearbyConnectionClient : INearbyConnectionClient { private volatile AndroidJavaObject mClient; private readonly static long NearbyClientId = 0L; private readonly static int ApplicationInfoFlags = 0x00000080; private readonly static string ServiceId = ReadServiceId(); protected IMessageListener mAdvertisingMessageListener; public AndroidNearbyConnectionClient() { PlayGamesHelperObject.CreateObject(); NearbyHelperObject.CreateObject(this); using (var nearbyClass = new AndroidJavaClass("com.google.android.gms.nearby.Nearby")) { mClient = nearbyClass.CallStatic("getConnectionsClient", AndroidHelperFragment.GetActivity()); } } public int MaxUnreliableMessagePayloadLength() { return NearbyConnectionConfiguration.MaxUnreliableMessagePayloadLength; } public int MaxReliableMessagePayloadLength() { return NearbyConnectionConfiguration.MaxReliableMessagePayloadLength; } public void SendReliable(List recipientEndpointIds, byte[] payload) { InternalSend(recipientEndpointIds, payload); } public void SendUnreliable(List recipientEndpointIds, byte[] payload) { InternalSend(recipientEndpointIds, payload); } private void InternalSend(List recipientEndpointIds, byte[] payload) { Misc.CheckNotNull(recipientEndpointIds); Misc.CheckNotNull(payload); using (var payloadClass = new AndroidJavaClass("com.google.android.gms.nearby.connection.Payload")) using (var payloadObject = payloadClass.CallStatic("fromBytes", payload)) using (var task = mClient.Call("sendPayload", AndroidJavaConverter.ToJavaStringList(recipientEndpointIds), payloadObject)) ; } public void StartAdvertising(string name, List appIdentifiers, TimeSpan? advertisingDuration, Action resultCallback, Action connectionRequestCallback) { Misc.CheckNotNull(resultCallback, "resultCallback"); Misc.CheckNotNull(connectionRequestCallback, "connectionRequestCallback"); if (advertisingDuration.HasValue && advertisingDuration.Value.Ticks < 0) { throw new InvalidOperationException("advertisingDuration must be positive"); } connectionRequestCallback = ToOnGameThread(connectionRequestCallback); resultCallback = ToOnGameThread(resultCallback); AdvertisingConnectionLifecycleCallbackProxy callbackProxy = new AdvertisingConnectionLifecycleCallbackProxy(resultCallback, connectionRequestCallback, this); using (var connectionLifecycleCallback = new AndroidJavaObject("com.google.games.bridge.ConnectionLifecycleCallbackProxy", callbackProxy)) using (var advertisingOptions = CreateAdvertisingOptions()) using (var task = mClient.Call("startAdvertising", name, GetServiceId(), connectionLifecycleCallback, advertisingOptions)) { AndroidTaskUtils.AddOnSuccessListener( task, v => NearbyHelperObject.StartAdvertisingTimer(advertisingDuration) ); } } private AndroidJavaObject CreateAdvertisingOptions() { using (var strategy = new AndroidJavaClass("com.google.android.gms.nearby.connection.Strategy") .GetStatic("P2P_CLUSTER")) using (var builder = new AndroidJavaObject("com.google.android.gms.nearby.connection.AdvertisingOptions$Builder")) using (builder.Call("setStrategy", strategy)) { return builder.Call("build"); } } private class AdvertisingConnectionLifecycleCallbackProxy : AndroidJavaProxy { private Action mResultCallback; private Action mConnectionRequestCallback; private AndroidNearbyConnectionClient mClient; private string mLocalEndpointName; public AdvertisingConnectionLifecycleCallbackProxy(Action resultCallback, Action connectionRequestCallback, AndroidNearbyConnectionClient client) : base( "com/google/games/bridge/ConnectionLifecycleCallbackProxy$Callback") { mResultCallback = resultCallback; mConnectionRequestCallback = connectionRequestCallback; mClient = client; } public void onConnectionInitiated(string endpointId, AndroidJavaObject connectionInfo) { mLocalEndpointName = connectionInfo.Call("getEndpointName"); mConnectionRequestCallback(new ConnectionRequest(endpointId, mLocalEndpointName, mClient.GetServiceId(), new byte[0])); } public void onConnectionResult(string endpointId, AndroidJavaObject connectionResolution) { int statusCode; using (var status = connectionResolution.Call("getStatus")) { statusCode = status.Call("getStatusCode"); } if (statusCode == 0) // STATUS_OK { mResultCallback(new AdvertisingResult(ResponseStatus.Success, mLocalEndpointName)); return; } if (statusCode == 8001) // STATUS_ALREADY_ADVERTISING { mResultCallback(new AdvertisingResult(ResponseStatus.NotAuthorized, mLocalEndpointName)); return; } mResultCallback(new AdvertisingResult(ResponseStatus.InternalError, mLocalEndpointName)); } public void onDisconnected(string endpointId) { if (mClient.mAdvertisingMessageListener != null) { mClient.mAdvertisingMessageListener.OnRemoteEndpointDisconnected(endpointId); } } } public void StopAdvertising() { mClient.Call("stopAdvertising"); mAdvertisingMessageListener = null; } public void SendConnectionRequest(string name, string remoteEndpointId, byte[] payload, Action responseCallback, IMessageListener listener) { Misc.CheckNotNull(listener, "listener"); var listenerOnGameThread = new OnGameThreadMessageListener(listener); DiscoveringConnectionLifecycleCallback cb = new DiscoveringConnectionLifecycleCallback(responseCallback, listenerOnGameThread, mClient); using (var connectionLifecycleCallback = new AndroidJavaObject("com.google.games.bridge.ConnectionLifecycleCallbackProxy", cb)) using (mClient.Call("requestConnection", name, remoteEndpointId, connectionLifecycleCallback)) ; } public void AcceptConnectionRequest(string remoteEndpointId, byte[] payload, IMessageListener listener) { Misc.CheckNotNull(listener, "listener"); mAdvertisingMessageListener = new OnGameThreadMessageListener(listener); using (var payloadCallback = new AndroidJavaObject("com.google.games.bridge.PayloadCallbackProxy", new PayloadCallback(listener))) using (mClient.Call("acceptConnection", remoteEndpointId, payloadCallback)) ; } private class PayloadCallback : AndroidJavaProxy { private IMessageListener mListener; public PayloadCallback(IMessageListener listener) : base( "com/google/games/bridge/PayloadCallbackProxy$Callback") { mListener = listener; } public void onPayloadReceived(String endpointId, AndroidJavaObject payload) { if (payload.Call("getType") != 1) // 1 for BYTES { return; } mListener.OnMessageReceived(endpointId, payload.Call("asBytes"), /* isReliableMessage */ true); } } public void StartDiscovery(string serviceId, TimeSpan? advertisingDuration, IDiscoveryListener listener) { Misc.CheckNotNull(serviceId, "serviceId"); Misc.CheckNotNull(listener, "listener"); var listenerOnGameThread = new OnGameThreadDiscoveryListener(listener); if (advertisingDuration.HasValue && advertisingDuration.Value.Ticks < 0) { throw new InvalidOperationException("advertisingDuration must be positive"); } using (var endpointDiscoveryCallback = new AndroidJavaObject( "com.google.games.bridge.EndpointDiscoveryCallbackProxy", new EndpointDiscoveryCallback(listenerOnGameThread))) using (var discoveryOptions = CreateDiscoveryOptions()) using (var task = mClient.Call("startDiscovery", serviceId, endpointDiscoveryCallback, discoveryOptions)) { AndroidTaskUtils.AddOnSuccessListener( task, v => NearbyHelperObject.StartDiscoveryTimer(advertisingDuration) ); } } private class DiscoveringConnectionLifecycleCallback : AndroidJavaProxy { private Action mResponseCallback; private IMessageListener mListener; private AndroidJavaObject mClient; public DiscoveringConnectionLifecycleCallback(Action responseCallback, IMessageListener listener, AndroidJavaObject client) : base( "com/google/games/bridge/ConnectionLifecycleCallbackProxy$Callback") { mResponseCallback = responseCallback; mListener = listener; mClient = client; } public void onConnectionInitiated(string endpointId, AndroidJavaObject connectionInfo) { using (var payloadCallback = new AndroidJavaObject("com.google.games.bridge.PayloadCallbackProxy", new PayloadCallback(mListener))) using (mClient.Call("acceptConnection", endpointId, payloadCallback)) ; } public void onConnectionResult(string endpointId, AndroidJavaObject connectionResolution) { int statusCode; using (var status = connectionResolution.Call("getStatus")) { statusCode = status.Call("getStatusCode"); } if (statusCode == 0) // STATUS_OK { mResponseCallback(ConnectionResponse.Accepted(NearbyClientId, endpointId, new byte[0])); return; } if (statusCode == 8002) // STATUS_ALREADY_DISCOVERING { mResponseCallback(ConnectionResponse.AlreadyConnected(NearbyClientId, endpointId)); return; } mResponseCallback(ConnectionResponse.Rejected(NearbyClientId, endpointId)); } public void onDisconnected(string endpointId) { mListener.OnRemoteEndpointDisconnected(endpointId); } } private AndroidJavaObject CreateDiscoveryOptions() { using (var strategy = new AndroidJavaClass("com.google.android.gms.nearby.connection.Strategy").GetStatic( "P2P_CLUSTER")) using (var builder = new AndroidJavaObject("com.google.android.gms.nearby.connection.DiscoveryOptions$Builder")) using (builder.Call("setStrategy", strategy)) { return builder.Call("build"); } } private class EndpointDiscoveryCallback : AndroidJavaProxy { private IDiscoveryListener mListener; public EndpointDiscoveryCallback(IDiscoveryListener listener) : base( "com/google/games/bridge/EndpointDiscoveryCallbackProxy$Callback") { mListener = listener; } public void onEndpointFound(string endpointId, AndroidJavaObject endpointInfo) { mListener.OnEndpointFound(CreateEndPointDetails(endpointId, endpointInfo)); } public void onEndpointLost(string endpointId) { mListener.OnEndpointLost(endpointId); } private EndpointDetails CreateEndPointDetails(string endpointId, AndroidJavaObject endpointInfo) { return new EndpointDetails( endpointId, endpointInfo.Call("getEndpointName"), endpointInfo.Call("getServiceId") ); } } private class OnGameThreadMessageListener : IMessageListener { private readonly IMessageListener mListener; public OnGameThreadMessageListener(IMessageListener listener) { mListener = Misc.CheckNotNull(listener); } public void OnMessageReceived(string remoteEndpointId, byte[] data, bool isReliableMessage) { PlayGamesHelperObject.RunOnGameThread(() => mListener.OnMessageReceived( remoteEndpointId, data, isReliableMessage)); } public void OnRemoteEndpointDisconnected(string remoteEndpointId) { PlayGamesHelperObject.RunOnGameThread( () => mListener.OnRemoteEndpointDisconnected(remoteEndpointId)); } } private class OnGameThreadDiscoveryListener : IDiscoveryListener { private readonly IDiscoveryListener mListener; public OnGameThreadDiscoveryListener(IDiscoveryListener listener) { mListener = listener; } public void OnEndpointFound(EndpointDetails discoveredEndpoint) { PlayGamesHelperObject.RunOnGameThread(() => mListener.OnEndpointFound(discoveredEndpoint)); } public void OnEndpointLost(string lostEndpointId) { PlayGamesHelperObject.RunOnGameThread(() => mListener.OnEndpointLost(lostEndpointId)); } } public void StopDiscovery(string serviceId) { mClient.Call("stopDiscovery"); } public void RejectConnectionRequest(string requestingEndpointId) { Misc.CheckNotNull(requestingEndpointId, "requestingEndpointId"); using (var task = mClient.Call("rejectConnection", requestingEndpointId)) ; } public void DisconnectFromEndpoint(string remoteEndpointId) { mClient.Call("disconnectFromEndpoint", remoteEndpointId); } public void StopAllConnections() { mClient.Call("stopAllEndpoints"); mAdvertisingMessageListener = null; } public string GetAppBundleId() { using (var activity = AndroidHelperFragment.GetActivity()) { return activity.Call("getPackageName"); } } public string GetServiceId() { return ServiceId; } private static string ReadServiceId() { using (var activity = AndroidHelperFragment.GetActivity()) { string packageName = activity.Call("getPackageName"); using (var pm = activity.Call("getPackageManager")) using (var appInfo = pm.Call("getApplicationInfo", packageName, ApplicationInfoFlags)) using (var bundle = appInfo.Get("metaData")) { string sysId = bundle.Call("getString", "com.google.android.gms.nearby.connection.SERVICE_ID"); OurUtils.Logger.d("SystemId from Manifest: " + sysId); return sysId; } } } private static Action ToOnGameThread(Action toConvert) { return (val) => PlayGamesHelperObject.RunOnGameThread(() => toConvert(val)); } private static Action ToOnGameThread(Action toConvert) { return (val1, val2) => PlayGamesHelperObject.RunOnGameThread(() => toConvert(val1, val2)); } } } #endif