AndroidNearbyConnectionClient.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. #if UNITY_ANDROID
  2. #pragma warning disable 0642 // Possible mistaken empty statement
  3. namespace GooglePlayGames.Android
  4. {
  5. using System;
  6. using System.Collections.Generic;
  7. using GooglePlayGames.BasicApi;
  8. using GooglePlayGames.BasicApi.Nearby;
  9. using GooglePlayGames.OurUtils;
  10. using UnityEngine;
  11. public class AndroidNearbyConnectionClient : INearbyConnectionClient
  12. {
  13. private volatile AndroidJavaObject mClient;
  14. private readonly static long NearbyClientId = 0L;
  15. private readonly static int ApplicationInfoFlags = 0x00000080;
  16. private readonly static string ServiceId = ReadServiceId();
  17. protected IMessageListener mAdvertisingMessageListener;
  18. public AndroidNearbyConnectionClient()
  19. {
  20. PlayGamesHelperObject.CreateObject();
  21. NearbyHelperObject.CreateObject(this);
  22. using (var nearbyClass = new AndroidJavaClass("com.google.android.gms.nearby.Nearby"))
  23. {
  24. mClient = nearbyClass.CallStatic<AndroidJavaObject>("getConnectionsClient",
  25. AndroidHelperFragment.GetActivity());
  26. }
  27. }
  28. public int MaxUnreliableMessagePayloadLength()
  29. {
  30. return NearbyConnectionConfiguration.MaxUnreliableMessagePayloadLength;
  31. }
  32. public int MaxReliableMessagePayloadLength()
  33. {
  34. return NearbyConnectionConfiguration.MaxReliableMessagePayloadLength;
  35. }
  36. public void SendReliable(List<string> recipientEndpointIds, byte[] payload)
  37. {
  38. InternalSend(recipientEndpointIds, payload);
  39. }
  40. public void SendUnreliable(List<string> recipientEndpointIds, byte[] payload)
  41. {
  42. InternalSend(recipientEndpointIds, payload);
  43. }
  44. private void InternalSend(List<string> recipientEndpointIds, byte[] payload)
  45. {
  46. Misc.CheckNotNull(recipientEndpointIds);
  47. Misc.CheckNotNull(payload);
  48. using (var payloadClass = new AndroidJavaClass("com.google.android.gms.nearby.connection.Payload"))
  49. using (var payloadObject = payloadClass.CallStatic<AndroidJavaObject>("fromBytes", payload))
  50. using (var task = mClient.Call<AndroidJavaObject>("sendPayload",
  51. AndroidJavaConverter.ToJavaStringList(recipientEndpointIds),
  52. payloadObject))
  53. ;
  54. }
  55. public void StartAdvertising(string name, List<string> appIdentifiers,
  56. TimeSpan? advertisingDuration, Action<AdvertisingResult> resultCallback,
  57. Action<ConnectionRequest> connectionRequestCallback)
  58. {
  59. Misc.CheckNotNull(resultCallback, "resultCallback");
  60. Misc.CheckNotNull(connectionRequestCallback, "connectionRequestCallback");
  61. if (advertisingDuration.HasValue && advertisingDuration.Value.Ticks < 0)
  62. {
  63. throw new InvalidOperationException("advertisingDuration must be positive");
  64. }
  65. connectionRequestCallback = ToOnGameThread(connectionRequestCallback);
  66. resultCallback = ToOnGameThread(resultCallback);
  67. AdvertisingConnectionLifecycleCallbackProxy callbackProxy =
  68. new AdvertisingConnectionLifecycleCallbackProxy(resultCallback, connectionRequestCallback, this);
  69. using (var connectionLifecycleCallback =
  70. new AndroidJavaObject("com.google.games.bridge.ConnectionLifecycleCallbackProxy", callbackProxy))
  71. using (var advertisingOptions = CreateAdvertisingOptions())
  72. using (var task = mClient.Call<AndroidJavaObject>("startAdvertising", name, GetServiceId(),
  73. connectionLifecycleCallback, advertisingOptions))
  74. {
  75. AndroidTaskUtils.AddOnSuccessListener<AndroidJavaObject>(
  76. task,
  77. v => NearbyHelperObject.StartAdvertisingTimer(advertisingDuration)
  78. );
  79. }
  80. }
  81. private AndroidJavaObject CreateAdvertisingOptions()
  82. {
  83. using (var strategy = new AndroidJavaClass("com.google.android.gms.nearby.connection.Strategy")
  84. .GetStatic<AndroidJavaObject>("P2P_CLUSTER"))
  85. using (var builder =
  86. new AndroidJavaObject("com.google.android.gms.nearby.connection.AdvertisingOptions$Builder"))
  87. using (builder.Call<AndroidJavaObject>("setStrategy", strategy))
  88. {
  89. return builder.Call<AndroidJavaObject>("build");
  90. }
  91. }
  92. private class AdvertisingConnectionLifecycleCallbackProxy : AndroidJavaProxy
  93. {
  94. private Action<AdvertisingResult> mResultCallback;
  95. private Action<ConnectionRequest> mConnectionRequestCallback;
  96. private AndroidNearbyConnectionClient mClient;
  97. private string mLocalEndpointName;
  98. public AdvertisingConnectionLifecycleCallbackProxy(Action<AdvertisingResult> resultCallback,
  99. Action<ConnectionRequest> connectionRequestCallback, AndroidNearbyConnectionClient client) : base(
  100. "com/google/games/bridge/ConnectionLifecycleCallbackProxy$Callback")
  101. {
  102. mResultCallback = resultCallback;
  103. mConnectionRequestCallback = connectionRequestCallback;
  104. mClient = client;
  105. }
  106. public void onConnectionInitiated(string endpointId, AndroidJavaObject connectionInfo)
  107. {
  108. mLocalEndpointName = connectionInfo.Call<string>("getEndpointName");
  109. mConnectionRequestCallback(new ConnectionRequest(endpointId, mLocalEndpointName, mClient.GetServiceId(),
  110. new byte[0]));
  111. }
  112. public void onConnectionResult(string endpointId, AndroidJavaObject connectionResolution)
  113. {
  114. int statusCode;
  115. using (var status = connectionResolution.Call<AndroidJavaObject>("getStatus"))
  116. {
  117. statusCode = status.Call<int>("getStatusCode");
  118. }
  119. if (statusCode == 0) // STATUS_OK
  120. {
  121. mResultCallback(new AdvertisingResult(ResponseStatus.Success, mLocalEndpointName));
  122. return;
  123. }
  124. if (statusCode == 8001) // STATUS_ALREADY_ADVERTISING
  125. {
  126. mResultCallback(new AdvertisingResult(ResponseStatus.NotAuthorized, mLocalEndpointName));
  127. return;
  128. }
  129. mResultCallback(new AdvertisingResult(ResponseStatus.InternalError, mLocalEndpointName));
  130. }
  131. public void onDisconnected(string endpointId)
  132. {
  133. if (mClient.mAdvertisingMessageListener != null)
  134. {
  135. mClient.mAdvertisingMessageListener.OnRemoteEndpointDisconnected(endpointId);
  136. }
  137. }
  138. }
  139. public void StopAdvertising()
  140. {
  141. mClient.Call("stopAdvertising");
  142. mAdvertisingMessageListener = null;
  143. }
  144. public void SendConnectionRequest(string name, string remoteEndpointId, byte[] payload,
  145. Action<ConnectionResponse> responseCallback, IMessageListener listener)
  146. {
  147. Misc.CheckNotNull(listener, "listener");
  148. var listenerOnGameThread = new OnGameThreadMessageListener(listener);
  149. DiscoveringConnectionLifecycleCallback cb =
  150. new DiscoveringConnectionLifecycleCallback(responseCallback, listenerOnGameThread, mClient);
  151. using (var connectionLifecycleCallback =
  152. new AndroidJavaObject("com.google.games.bridge.ConnectionLifecycleCallbackProxy", cb))
  153. using (mClient.Call<AndroidJavaObject>("requestConnection", name, remoteEndpointId,
  154. connectionLifecycleCallback))
  155. ;
  156. }
  157. public void AcceptConnectionRequest(string remoteEndpointId, byte[] payload, IMessageListener listener)
  158. {
  159. Misc.CheckNotNull(listener, "listener");
  160. mAdvertisingMessageListener = new OnGameThreadMessageListener(listener);
  161. using (var payloadCallback = new AndroidJavaObject("com.google.games.bridge.PayloadCallbackProxy",
  162. new PayloadCallback(listener)))
  163. using (mClient.Call<AndroidJavaObject>("acceptConnection", remoteEndpointId, payloadCallback))
  164. ;
  165. }
  166. private class PayloadCallback : AndroidJavaProxy
  167. {
  168. private IMessageListener mListener;
  169. public PayloadCallback(IMessageListener listener) : base(
  170. "com/google/games/bridge/PayloadCallbackProxy$Callback")
  171. {
  172. mListener = listener;
  173. }
  174. public void onPayloadReceived(String endpointId, AndroidJavaObject payload)
  175. {
  176. if (payload.Call<int>("getType") != 1) // 1 for BYTES
  177. {
  178. return;
  179. }
  180. mListener.OnMessageReceived(endpointId, payload.Call<byte[]>("asBytes"), /* isReliableMessage */ true);
  181. }
  182. }
  183. public void StartDiscovery(string serviceId, TimeSpan? advertisingDuration,
  184. IDiscoveryListener listener)
  185. {
  186. Misc.CheckNotNull(serviceId, "serviceId");
  187. Misc.CheckNotNull(listener, "listener");
  188. var listenerOnGameThread = new OnGameThreadDiscoveryListener(listener);
  189. if (advertisingDuration.HasValue && advertisingDuration.Value.Ticks < 0)
  190. {
  191. throw new InvalidOperationException("advertisingDuration must be positive");
  192. }
  193. using (var endpointDiscoveryCallback = new AndroidJavaObject(
  194. "com.google.games.bridge.EndpointDiscoveryCallbackProxy",
  195. new EndpointDiscoveryCallback(listenerOnGameThread)))
  196. using (var discoveryOptions = CreateDiscoveryOptions())
  197. using (var task = mClient.Call<AndroidJavaObject>("startDiscovery", serviceId, endpointDiscoveryCallback,
  198. discoveryOptions))
  199. {
  200. AndroidTaskUtils.AddOnSuccessListener<AndroidJavaObject>(
  201. task,
  202. v => NearbyHelperObject.StartDiscoveryTimer(advertisingDuration)
  203. );
  204. }
  205. }
  206. private class DiscoveringConnectionLifecycleCallback : AndroidJavaProxy
  207. {
  208. private Action<ConnectionResponse> mResponseCallback;
  209. private IMessageListener mListener;
  210. private AndroidJavaObject mClient;
  211. public DiscoveringConnectionLifecycleCallback(Action<ConnectionResponse> responseCallback,
  212. IMessageListener listener, AndroidJavaObject client) : base(
  213. "com/google/games/bridge/ConnectionLifecycleCallbackProxy$Callback")
  214. {
  215. mResponseCallback = responseCallback;
  216. mListener = listener;
  217. mClient = client;
  218. }
  219. public void onConnectionInitiated(string endpointId, AndroidJavaObject connectionInfo)
  220. {
  221. using (var payloadCallback = new AndroidJavaObject("com.google.games.bridge.PayloadCallbackProxy",
  222. new PayloadCallback(mListener)))
  223. using (mClient.Call<AndroidJavaObject>("acceptConnection", endpointId, payloadCallback))
  224. ;
  225. }
  226. public void onConnectionResult(string endpointId, AndroidJavaObject connectionResolution)
  227. {
  228. int statusCode;
  229. using (var status = connectionResolution.Call<AndroidJavaObject>("getStatus"))
  230. {
  231. statusCode = status.Call<int>("getStatusCode");
  232. }
  233. if (statusCode == 0) // STATUS_OK
  234. {
  235. mResponseCallback(ConnectionResponse.Accepted(NearbyClientId, endpointId, new byte[0]));
  236. return;
  237. }
  238. if (statusCode == 8002) // STATUS_ALREADY_DISCOVERING
  239. {
  240. mResponseCallback(ConnectionResponse.AlreadyConnected(NearbyClientId, endpointId));
  241. return;
  242. }
  243. mResponseCallback(ConnectionResponse.Rejected(NearbyClientId, endpointId));
  244. }
  245. public void onDisconnected(string endpointId)
  246. {
  247. mListener.OnRemoteEndpointDisconnected(endpointId);
  248. }
  249. }
  250. private AndroidJavaObject CreateDiscoveryOptions()
  251. {
  252. using (var strategy =
  253. new AndroidJavaClass("com.google.android.gms.nearby.connection.Strategy").GetStatic<AndroidJavaObject>(
  254. "P2P_CLUSTER"))
  255. using (var builder =
  256. new AndroidJavaObject("com.google.android.gms.nearby.connection.DiscoveryOptions$Builder"))
  257. using (builder.Call<AndroidJavaObject>("setStrategy", strategy))
  258. {
  259. return builder.Call<AndroidJavaObject>("build");
  260. }
  261. }
  262. private class EndpointDiscoveryCallback : AndroidJavaProxy
  263. {
  264. private IDiscoveryListener mListener;
  265. public EndpointDiscoveryCallback(IDiscoveryListener listener) : base(
  266. "com/google/games/bridge/EndpointDiscoveryCallbackProxy$Callback")
  267. {
  268. mListener = listener;
  269. }
  270. public void onEndpointFound(string endpointId, AndroidJavaObject endpointInfo)
  271. {
  272. mListener.OnEndpointFound(CreateEndPointDetails(endpointId, endpointInfo));
  273. }
  274. public void onEndpointLost(string endpointId)
  275. {
  276. mListener.OnEndpointLost(endpointId);
  277. }
  278. private EndpointDetails CreateEndPointDetails(string endpointId, AndroidJavaObject endpointInfo)
  279. {
  280. return new EndpointDetails(
  281. endpointId,
  282. endpointInfo.Call<string>("getEndpointName"),
  283. endpointInfo.Call<string>("getServiceId")
  284. );
  285. }
  286. }
  287. private class OnGameThreadMessageListener : IMessageListener
  288. {
  289. private readonly IMessageListener mListener;
  290. public OnGameThreadMessageListener(IMessageListener listener)
  291. {
  292. mListener = Misc.CheckNotNull(listener);
  293. }
  294. public void OnMessageReceived(string remoteEndpointId, byte[] data,
  295. bool isReliableMessage)
  296. {
  297. PlayGamesHelperObject.RunOnGameThread(() => mListener.OnMessageReceived(
  298. remoteEndpointId, data, isReliableMessage));
  299. }
  300. public void OnRemoteEndpointDisconnected(string remoteEndpointId)
  301. {
  302. PlayGamesHelperObject.RunOnGameThread(
  303. () => mListener.OnRemoteEndpointDisconnected(remoteEndpointId));
  304. }
  305. }
  306. private class OnGameThreadDiscoveryListener : IDiscoveryListener
  307. {
  308. private readonly IDiscoveryListener mListener;
  309. public OnGameThreadDiscoveryListener(IDiscoveryListener listener)
  310. {
  311. mListener = listener;
  312. }
  313. public void OnEndpointFound(EndpointDetails discoveredEndpoint)
  314. {
  315. PlayGamesHelperObject.RunOnGameThread(() => mListener.OnEndpointFound(discoveredEndpoint));
  316. }
  317. public void OnEndpointLost(string lostEndpointId)
  318. {
  319. PlayGamesHelperObject.RunOnGameThread(() => mListener.OnEndpointLost(lostEndpointId));
  320. }
  321. }
  322. public void StopDiscovery(string serviceId)
  323. {
  324. mClient.Call("stopDiscovery");
  325. }
  326. public void RejectConnectionRequest(string requestingEndpointId)
  327. {
  328. Misc.CheckNotNull(requestingEndpointId, "requestingEndpointId");
  329. using (var task = mClient.Call<AndroidJavaObject>("rejectConnection", requestingEndpointId)) ;
  330. }
  331. public void DisconnectFromEndpoint(string remoteEndpointId)
  332. {
  333. mClient.Call("disconnectFromEndpoint", remoteEndpointId);
  334. }
  335. public void StopAllConnections()
  336. {
  337. mClient.Call("stopAllEndpoints");
  338. mAdvertisingMessageListener = null;
  339. }
  340. public string GetAppBundleId()
  341. {
  342. using (var activity = AndroidHelperFragment.GetActivity())
  343. {
  344. return activity.Call<string>("getPackageName");
  345. }
  346. }
  347. public string GetServiceId()
  348. {
  349. return ServiceId;
  350. }
  351. private static string ReadServiceId()
  352. {
  353. using (var activity = AndroidHelperFragment.GetActivity())
  354. {
  355. string packageName = activity.Call<string>("getPackageName");
  356. using (var pm = activity.Call<AndroidJavaObject>("getPackageManager"))
  357. using (var appInfo =
  358. pm.Call<AndroidJavaObject>("getApplicationInfo", packageName, ApplicationInfoFlags))
  359. using (var bundle = appInfo.Get<AndroidJavaObject>("metaData"))
  360. {
  361. string sysId = bundle.Call<string>("getString",
  362. "com.google.android.gms.nearby.connection.SERVICE_ID");
  363. OurUtils.Logger.d("SystemId from Manifest: " + sysId);
  364. return sysId;
  365. }
  366. }
  367. }
  368. private static Action<T> ToOnGameThread<T>(Action<T> toConvert)
  369. {
  370. return (val) => PlayGamesHelperObject.RunOnGameThread(() => toConvert(val));
  371. }
  372. private static Action<T1, T2> ToOnGameThread<T1, T2>(Action<T1, T2> toConvert)
  373. {
  374. return (val1, val2) => PlayGamesHelperObject.RunOnGameThread(() => toConvert(val1, val2));
  375. }
  376. }
  377. }
  378. #endif