[Huawei Developers] Jack Wu Shenzhen:
Imagine these scenarios. When you transfer a file to your friend using a mobile phone, the progress seems never to reach the end. Finally, the transfer ends, but a single file has exhausted your mobile data. Or you are on a flight with your friends and feel bored, but you cannot play a multi-player game together because there is no Internet connection.

[IMG="1"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/005/796/051/2640852000005796051.20200709020200.08419906878130787848176630094768:50510709080006:2800:B7984240F3F213AB88993492C2A2F1DD89CB325685C9ECF60CAFE7BA47D5C672.jpg[/IMG]

You are so near, yet you cannot transmit data to each other's device conveniently. Nearby Service is designed to help you resolve these pain points. You only need to integrate it, its Nearby Connection capability will allow your users to transfer data in forms like text, streams, and files. Typical use cases of this capability include multi-player gaming, real-time collaboration, multi-screen gaming, and offline file transfer.

The following figure shows how the function works.

If you are interested in the implementation details, download the source code from GitHub: https://github.com/HMS-Core/hms-nearby-demo/tree/master/NearbyConnection

The following will elaborate on the development process of Nearby Connection.

1. Service Process

[IMG="2"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/006/560/358/2640852000006560358.20200708092616.76376553497909750455685571211598:50510709080006:2800:FE171488C2420E475DE399E867B7C3A910742D363797F23312FFE4C26B281A3B.jpg[/IMG]

The process can be divided into four phases.

Broadcast and scanning phase: The broadcaster starts broadcasting, and the discoverer starts scanning to discover the broadcaster.

1) The broadcaster calls startBroadcasting() to start broadcasting.

2) The discoverer calls startScan() to start scanning for nearby devices.

3) The onFound() method notifies the scanning result.

Connection setup phase: The discoverer initiates a connection and starts a symmetric authentication process, and the two endpoints independently accept or reject the connection request.

1) The discoverer calls requestConnect() to initiate a connection request to the broadcaster.

2) After onEstablish() notifies the two endpoints of the connection startup, the two endpoints can call acceptConnect() to accept the connection or call rejectConnect() to reject the connection.

3) The onResult() method notifies the two endpoints of the connection result. The connection can be set up only when both endpoints accept the connection.

Data transmission phase: After the connection is set up, the two endpoints start data exchange.

1） After the connection is set up, both endpoints can call sendData() to send data to the remote endpoint.

2） The data subscriber is notified by onReceived() that the data is received. Both endpoints are notified by onTransferUpdate() of the current transmission status.

Disconnection phase: Either endpoint initiates a disconnection request to notify the remote endpoint of the disconnection.

1） The endpoint that actively disconnects from the remote endpoint calls disconnect() to disconnect from the remote endpoint. The remote endpoint is notified by onDisconnected() of the disconnection.

2. Development Procedure

2.1 Getting Started

If you are new to Huawei Mobile Services (HMS), you need to configure app information in AppGallery Connect, enable Nearby Service on the HUAWEI Developers console, and integrate the HMS Core SDK. For details, please refer to the documentation.

2.2 Declaring System Permissions

To use Nearby Discovery and Nearby Transfer APIs in Nearby Connection development scenarios, your app must declare the appropriate permissions based on the policies used. For example, to use the POLICY_STAR policy to develop a file transfer app, you need to add specific permissions to the AndroidManifest.xml file.

<!-- Required for Nearby Discovery and Nearby Transfer -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- Required for FILE payloads -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

The ACCESS_FINE_LOCATION, WRITE_EXTERNAL_STORAGE, and READ_EXTERNAL_STORAGE permissions are dangerous system permissions, so you must dynamically apply for these permissions. If any required permission is not available, Nearby Service will reject the application to enable broadcast or scanning.

2.3 Selecting a Policy

Nearby Discovery supports three connection policies: POLICY_MESH, POLICY_STAR, and POLICY_P2P. You can select a policy based on the actual scenario.

The sample code for selecting a policy and creating the BroadcastOption object is as follows:

Policy policy = Policy.POLICY_STAR;
BroadcastOption broadcastOption = new BroadcastOption.Builder().setPolicy (policy).build();

2.4 Broadcast and Scanning

Once you grant the permissions required by the app and select a policy for your app, you can start broadcast and scanning to find nearby devices.

2.4.1 Starting Broadcast

The broadcaster uses the selected policy and serviceId parameters to call startBroadcasting() to start broadcasting. The serviceId parameter uniquely identifies your app. You are advised to use the package name of your app as the value of serviceId, for example, com.huawei.example.myapp.

Sample code:

private void doStartBroadcasting() {
Policy policy = Policy.POLICY_STAR;
BroadcastOption broadcastOption = new BroadcastOption.Builder().setPolicy(policy).build();
Nearby.getDiscoveryEngine(getApplicationContext())
.startBroadcasting(name, serviceId, connectCallback, broadcastOption)
.addOnSuccessListener(
new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
/* We are broadcasting. */
}
})
.addOnFailureListener(
new OnFailureListener() {
@Override
public void onFailure(Exception e) {
/* Fail to start broadcasting. */
}
});
}

connectCallback is a connection listening callback class instance that notifies you of the connection status. For details about the connectCallback class and sample code, please refer to section 2.5.2.

2.4.2 Starting Scanning

The discoverer uses the selected policy and serviceId parameters to call startScan() to start scanning to discover nearby devices.

Sample code:

private void doStartScan() {
Policy policy = Policy.POLICY_STAR;
ScanOption scanOption = new ScanOption.Builder().setPolicy(policy).build();
Nearby.getDiscoveryEngine(getApplicationContext())
.startScan(serviceId, scanEndpointCallback, scanOption)
.addOnSuccessListener(
new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
/* Start scan success. */
}
})
.addOnFailureListener(
new OnFailureListener() {
@Override
public void onFailure(Exception e) {
/* Fail to start scan. */
}
});
}

In the preceding information, scanEndpointCallback is a scanning listening callback class instance that notifies the discoverer of the scanning result, for example, a device is discovered or a device is lost.

private ScanEndpointCallback scanEndpointCallback =
new ScanEndpointCallback() {
@Override
public void onFound(String endpointId, ScanEndpointInfo discoveryEndpointInfo) {
mEndpointId = endpointId;
mDiscoveryEngine.requestConnect(myNameStr, mEndpointId, mConnCb);
}
@Override
public void onLost(String endpointId) {
Log.d(TAG, "Nearby Connection Demo app: Lost endpoint: " + endpointId);
}
};

2.4.3 Stopping Broadcast

To stop broadcast, you can call stopBroadcasting(). The broadcaster may still receive a connection request from the discoverer afterward.

2.4.4 Stopping Scanning

To stop scanning, you can call stopScan(). The discoverer can still request a connection to a discovered device afterward. Generally, once you find the device you want to connect to, call stopScan() to stop scanning.

2.5 Connection Setup

2.5.1 Requesting a Connection

When nearby devices are found, the discoverer can call requestConnect() to initiate connections.

Sample code:

private void doStartConnect(String name, String endpointId) throws RemoteException {
Nearby.getDiscoveryEngine(getApplicationContext())
.requestConnect(name, endpointId, connectCallback)
.addOnSuccessListener(
new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
/* Request success. */
}
})
.addOnFailureListener(
new OnFailureListener() {
@Override
public void onFailure(Exception e) {
/* Fail to request connect. */
}
});
}

You can show the list of discovered devices to users and allow them to choose which devices to connect to based on your requirements.

2.5.2 Confirming the Connection

After the discoverer requests to set up a connection with the broadcaster, the discoverer notifies the two parties of the connection setup by calling back the onEstablish() method of connectCallback. Both parties must accept the connection by calling acceptConnect() or reject the connection by calling rejectConnect(). The connection can be set up only when both endpoints accept the connection. If one or both of them choose to reject the connection, the connection fails. In either mode, the connection result is notified through the onResult() method.

Sample code:

private final ConnectCallback connectCallback =
new ConnectCallback() {
@Override
public void onEstablish(String endpointId, ConnectInfo connectInfo) {
/* Accept the connection request without notifying user. */
Nearby.getDiscoveryEngine(getApplicationContext())
.acceptConnect(endpointId, dataCallback);
}
@Override
public void onResult(String endpointId, ConnectResult result) {
switch (result.getStatus().getStatusCode()) {
case StatusCode.STATUS_SUCCESS:
/* The connection was established successfully, we can exchange data. */
break;
case StatusCode.STATUS_CONNECT_REJECTED:
/* The Connection was rejected. */
break;
default:
/* other unknown status code. */
}
}
@Override
public void onDisconnected(String endpointId) {
/* The connection was disconneted. */
}
};

This example shows a way for both parties to accept the connection automatically. You can use other methods to confirm the connection as required.

2.5.3 Verifying the Connection

Your app can provide a way for users to confirm the connection to a specified device. For example, you can use an authentication token, which may be a short random character string or number. Typically, this involves displaying the token on two devices and requiring the user to enter or confirm the token manually, similar to a Bluetooth pairing dialog box.

The following describes how to authenticate the connection by confirming the pairing code in a dialog box:

Sample code:

@Override
public void onEstablish(String endpointId, ConnectInfo connectInfo) {
AlertDialog.Builder builder = new AlertDialog.Builder(getApplicationContext());
builder.setTitle(connectInfo.getEndpointName() + " request connection")
.setMessage("Please confirm the match code is: " + connectInfo.getAuthCode())
.setPositiveButton(
"Accept",
(DialogInterface dialog, int which) ->
/* Accept the connection. */
Nearby.getDiscoveryEngine(getApplicationContext())
.acceptConnect(endpointId, dataCallback))
.setNegativeButton(
"Reject",
(DialogInterface dialog, int which) ->
/* Reject the connection. */
Nearby.getDiscoveryEngine(getApplicationContext())
.rejectConnect(endpointId))
.setIcon(android.R.drawable.ic_dialog_alert);
AlertDialog alert = builder.create();
alert.show();
}

2.6 Transmitting Data

After devices are connected, data objects can be transmitted through the connection. Data objects are classified into byte arrays, files, and streams. You can call sendData() to send data and call onReceived() of DataCallback to receive data.

2.6.1 Data Types

1)	BYTES

You can call Data.fromBytes() to create a data object of the Data.Type.BYTES type.

The following is the sample code for sending byte arrays:

Data bytesData = Data.fromBytes(new byte[] {0xA, 0xA, 0xA, 0xA, 0xA});
Nearby.getTransferEngine(getApplicationContext()).sendData(toEndpointId, bytesData);

Note: The size of byte arrays cannot exceed 32 KB.

The following is the sample code for receiving byte arrays:

static class BytesDataReceiver extends DataCallback {
@Override
public void onReceived(String endpointId, Data data) {
/* BYTES data is sent as a single block, so we can get complete data. */
if (data.getType() == Data.Type.BYTES) {
byte[] receivedBytes = data.asBytes();
}
}
@Override
public void onTransferUpdate(String endpointId, TransferStateUpdate update) {
/* We will receive TRANSFER_STATE_SUCCESS update after onReceived() called. */
}
}

Note: Different from data of FILE and STREAM types, data of the BYTES type is sent as chunks, and the subscriber does not need to wait for the TRANSFER_STATE_SUCCESS status code. When onReceived() is called, you can call data.asBytes() to obtain the full data.

2)	FILE

You can call Data.fromFile() to create a data object of the Data.Type.FILE type.

The following is the sample code for sending a file:

File fileToSend = new File(getApplicationContext().getFilesDir(), "fileSample.txt");
try {
Data fileData = Data.fromFile(fileToSend);
Nearby.getTransferEngine(getApplicationContext())
.sendData(endpointList, fileData);
} catch (FileNotFoundException e) {
/* Exception handle. */
}

To be more efficient, you can create a FILE type using ParcelFileDescriptor, which minimizes file replication.

Sample code:

ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "r");
Data fileData = Data.fromFile(pfd);

When a file is received, it is saved in the Download directory on the subscriber's device with the name defined as the character string converted by fileData.getId(). After the transmission is complete, you can obtain the FILE object.

Sample code:

/* We can get the received file in the Download folder. */
File payloadFile = fileData.asFile().asJavaFile();

3)	STREAM

You can call Data.fromStream() to create a data object of the Data.Type.STREAM type.

Sample code:

URL url = new URL("https://developers.huawei.com");
Data streamData = Data.fromStream(url.openStream());
Nearby.getTransferEngine(getApplicationContext()).sendData(toEndpointId, streamData);

When the onTransferUpdate() callback is successful, the subscriber can call streamData.asStream().asInputStream() or streamData.asStream().asParcelFileDescriptor() to obtain the stream object.

Sample code:

static class StreamDataReceiver extends DataCallback {
private final HashMap<Long, Data> incomingData = new HashMap<>();
@Override
public void onTransferUpdate(String endpointId, TransferStateUpdate update) {
if (update.getStatus() == TransferStateUpdate.Status.TRANSFER_STATE_SUCCESS) {
Data data = incomingData.get(update.getDataId());
InputStream inputStream = data.asStream().asInputStream();
/* Further processing... */
}
}
@Override
public void onReceived(String endpointId, Data data) {
incomingData.put(data.getId(), data);
}
}

2.6.2 Updating the Data Transmission Progress

The onTransferUpdate() method in the DataCallBack callback class provides the data sending or receiving progress update. Both parties can display the transmission progress to users in forms like a progress bar.

2.6.3 Canceling Data Transmission

To cancel the transmission during data receiving or sending, you can call the cancelDataTransfer() method of the TransferEngine class.

2.7 Disconnecting from the Remote Endpoint

To disconnect from the remote endpoint, you can call the disconnect() method of the DiscoveryEngine class. Once this API is called, this endpoint cannot send or receive data.

Conclusion

Using Nearby Connection, you can implement the following functions for your app:

1. Local multi-player gaming: Players can connect with each other's device flexibly and enjoy a smooth gaming experience enabled by low-latency, stable, and reliable data transmission.

2. Offline file transfer: Files can be transferred at a speed of up to 80 Mbit/s without consuming any mobile data.

If you are interested and want to learn more, check our development guide at https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/introduction-0000001050040566.