More information like this, you can visit HUAWEI Developer Forum
Introduction to Nearby Connection:
Nearby Connection is one of the API of HUAWEI Nearby Service that allows applications to easily discover and connect to nearby devices, and exchange data with nearby devices without having Internet.
Example: We can chat to a friend and can share files such as photos and videos without connecting to the Internet.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Prerequisites:
Java JDK (1.8 or later)
Android (API level 21 or higher)
HMS Core (APK) 4.0.0.300 or later
Android Studio 3.X
Two Huawei phones (with USB cables) or Non Huawei Phone with updated version of HMS Core App for app running and debugging.
Integration:
1. Create a project in android studio and Huawei AGC.
2. Provide the SHA-256 Key in App Information Section.
3. Enable Huawei Nearby Service.
4. Download the agconnect-services.json from AGCand save into app directory.
5. In root build.gradle
Navigate to allprojects->repositories and buildscript->repositories and add the given line.
Code:
maven { url 'http://developer.huawei.com/repo/' }
In dependency add class path.
Code:
classpath 'com.huawei.agconnect:agcp:1.2.1.301'
6. In app build.gradle
Add compatibility mode to JDK 1.8
Code:
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
Add Implementation
Code:
implementation 'com.huawei.hms:nearby:5.0.1.300'
Apply plugin
Code:
apply plugin: 'com.huawei.agconnect'
7. Permissions in Manifest
Code:
<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" />
Nearby Connection Process:
Nearby connection process can be divided into four phases.
1. Broadcast and scanning
2. Connection setup
3. Data transmission
4. Disconnection
Let us go to the details.
1) Broadcast and scanning
This is the initial phase, here the broadcaster starts broadcasting, and the discoverer starts scanning to discover the broadcaster.
We can classify the functionalities of the initial phase as given:
a) Starting Broadcasting:
The broadcaster calls startBroadcasting() to start broadcasting
Code:
public void doStartBroadcasting() {
BroadcastOption.Builder advBuilder = new BroadcastOption.Builder();
advBuilder.setPolicy(Policy.POLICY_STAR);
discoveryEngine.startBroadcasting(myNameStr, myServiceId, connectCallback, advBuilder.build());
}
b) Starting Scanning:
The discoverer calls startScan() to start scanning to discover nearby devices.
Code:
public void doStartScan() {
ScanOption.Builder discBuilder = new ScanOption.Builder();
discBuilder.setPolicy(Policy.POLICY_STAR);
discoveryEngine.startScan(myServiceId, scanEndpointCallback, discBuilder.build());
}
c) Notifies the scanning result:
The scanning listening callback class instance notifies the discoverer of the scanning result. For example, a device is discovered or a device is lost, onFound() and onLost() methods are used here for the purpose.
Code:
private ScanEndpointCallback scanEndpointCallback =
new ScanEndpointCallback() {
@Override
public void onFound(String endpointId, ScanEndpointInfo discoveryEndpointInfo) {
mEndpointId = endpointId;
discoveryEngine.requestConnect(myNameStr, mEndpointId, connectCallback);
}
@Override
public void onLost(String endpointId) {
Log.d(TAG, "Nearby Connection Demo app: Lost endpoint: " + endpointId);
}
};
d) Stopping Broadcasting:
To stop broadcasting, we can call stopBroadcasting() method. The broadcaster cannot receive a connection request from the discoverer afterward.
Code:
discoveryEngine.stopBroadcasting();
e) Stopping Scanning:
To stop scanning, we can call stopScan() method. Once you find the device you want to connect to, call stopScan() to stop scanning.
Code:
discoveryEngine.stopScan();
2) Connection setup
In this phase the discoverer initiates a connection and starts a symmetric authentication process, and the two endpoints independently accept or reject the connection request.
We can classify the functionalities of the second phase as given:
a) Initiating a Connection:
When nearby devices are found, the discoverer can call requestConnect() to initiate connections request to the broadcaster.
Code:
discoveryEngine.requestConnect(myNameStr, mEndpointId, connectCallback);
b) 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 parties 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.
Code:
private ConnectCallback connectCallback =
new ConnectCallback() {
@Override
public void onEstablish(String endpointId, ConnectInfo connectionInfo) {
transferEngine = Nearby.getTransferEngine(getApplicationContext());
discoveryEngine.acceptConnect(endpointId, dataCallback);
ToastUtil.showShortToastTop(getApplicationContext(), "Let's chat!");
sendBtn.setEnabled(true);
msgEt.setEnabled(true);
connectBtn.setEnabled(false);
connectTaskResult = StatusCode.STATUS_SUCCESS;
if (myNameStr.compareTo(friendNameStr) > 0) {
discoveryEngine.stopScan();
} else {
discoveryEngine.stopBroadcasting();
}
}
@Override
public void onResult(String endpointId, ConnectResult resolution) {
mEndpointId = endpointId;
}
@Override
public void onDisconnected(String endpointId) {
ToastUtil.showShortToastTop(getApplicationContext(), "Disconnect.");
connectTaskResult = StatusCode.STATUS_NOT_CONNECTED;
sendBtn.setEnabled(false);
connectBtn.setEnabled(true);
msgEt.setEnabled(false);
myNameEt.setEnabled(true);
friendNameEt.setEnabled(true);
}
};
3) Data transmission:
After the connection is set up, the two endpoints start data exchange in this phase.
We can classify the functionalities of the third phase as given:
a) Sending data:
After the connection is set up, both endpoints can call sendData() to send data to the remote endpoint.
Code:
private void sendMessage() {
msgStr = msgEt.getText().toString() ;
Data data = Data.fromBytes(msgStr.getBytes(Charset.defaultCharset()));
Log.d(TAG, "myEndpointId " + mEndpointId);
transferEngine.sendData(mEndpointId, data).addOnCompleteListener(task -> {
task.addOnSuccessListener(su -> {
Log.i(TAG, "sendData success. Message:" + msgStr);
}).addOnFailureListener(e -> {
Log.e(TAG, "sendData failed, Message:" + msgStr + "cause: " + e.getMessage());
});
});
MessageBean item = new MessageBean();
item.setMyName(myNameStr);
item.setFriendName(friendNameStr);
item.setMsg(msgStr.split(":")[0]);
item.setType(MessageBean.TYPE_SEND_TEXT);
msgList.add(item);
adapter.notifyDataSetChanged();
msgEt.setText("");
messageListView.setSelection(messageListView.getBottom());
}
b) Receiving data:
The data subscriber is notified by onReceived() that the data is received.
Code:
@Override
public void onReceived(String string, Data data) {
Log.d(TAG, "onPayloadReceived, payload.getType() = " + data.getType());
Log.d(TAG, "onPayloadReceived, string=" + string);
switch (data.getType()) {
case Data.Type.BYTES:
msgStr = new String(data.asBytes(), UTF_8);
Log.d(TAG, "onReceived success. msgSt: " + msgStr);
MessageBean item = new MessageBean();
item.setMyName(myNameStr);
item.setFriendName(friendNameStr);
item.setMsg(msgStr);
item.setType(MessageBean.TYPE_RECEIVE_TEXT);
msgList.add(item);
adapter.notifyDataSetChanged();
messageListView.setSelection(messageListView.getBottom());
break;
default:
Log.i(TAG, "the other Unknown data type.");
return;
}
c) Updating the Progress:
The onTransferUpdate() method provides the data sending or receiving progress update.
Code:
@Override
public void onTransferUpdate(String string, TransferStateUpdate update) {
long payloadId = update.getDataId();
Log.d(TAG, "onTransferUpdate.payloadId:" + payloadId);
if (update.getStatus() == TransferStateUpdate.Status.TRANSFER_STATE_SUCCESS) {
Log.d(TAG, "PayloadTransferUpdate.Status.SUCCESS");
Data payload = incomingFilePayloads.remove(payloadId);
if (payload != null) {
Log.d(TAG, "payload.getType() " + payload.getType());
completedFilePayloads.put(payloadId, payload);
}
} else {
Log.d(TAG, "PayloadTransferUpdate.Status=" + update.getStatus());
}
}
d) Cancelling Transmission:
To cancel the transmission during data receiving or sending, we can call the cancelDataTransfer() method of the TransferEngine class.
4) Disconnection
This is final phase, here we can call the disconnect() method of the DiscoveryEngine class to disconnect from the remote endpoint.
Code:
discoveryEngine.disconnectAll();
The remote endpoint is notified by onDisconnected() of the disconnection. Once this API is called, this endpoint cannot send or receive data.
Code:
@Override
public void onDisconnected(String endpointId) {
ToastUtil.showShortToastTop(getApplicationContext(), "Disconnect.");
connectTaskResult = StatusCode.STATUS_NOT_CONNECTED;
sendBtn.setEnabled(false);
connectBtn.setEnabled(true);
msgEt.setEnabled(false);
myNameEt.setEnabled(true);
friendNameEt.setEnabled(true);
}
Screenshots:
Reference:
https://developer.huawei.com/consumer/en/doc/development/HMS-Guides/Nearby-Service-Nearby-Connection-Service-Nearby-Connection
Related
Huawei's Nearby SDK is sort of like Android Beam on steroids. Apps implementing it can use it for local file transfer, but that's not all. Nearby also enables apps to do realtime local communication, which is useful for things like locak multiplayer gaming. Finally, it also supports messaging, in the form of "beacons" that an implementing app can use to retrieve relevant localized information.
If any of this seems like it could be useful for you, read on, cause we're going to implement it.
Preparation
First up, make sure you have a Huawei Developer Account. This process can take a couple days, and you'll need one to use this SDK, so be sure to start that as soon as possible. You can sign up at https://developer.huawei.com.
Next, you'll want to obtain the SHA-256 representation of your app's signing key. If you don't have a signing key yet, be sure to create one before continuing. To obtain your signing key's SHA-256, you'll need to use Keytool which is part of the JDK installation. Keytool is a command-line program. If you're on Windows, open CMD. If you're on Linux, open Terminal.
On Windows, you'll need to "cd" into the directory containing the Keytool executable. For example, if you have JDK 1.8 v231 installed, Keytool will be located at the following path:
Code:
C:\Program Files\Java\jdk1.8.0_231\bin\
Once you find the directory, "cd" into it:
Code:
C: #Make sure you're in the right drive
cd C:\Program Files\Java\jdk1.8.0_231\bin\
Next, you need to find the location of your keystore. Using Android's debug keystore as an example, where the Android SDK is hosted on the "E:" drive in Windows, the path will be as follows:
Code:
E:\AndroidSDK\.android\debug.keystore
(Keytool also supports JKS-format keystores.)
Now you're ready to run the command. On Windows, it'll look something like this:
Code:
keytool -list -v -keystore E:\AndroidSDK\.android\debug.keystore
On Linux, the command should be similar, just using UNIX-style paths instead.
Enter the keystore password, and the key name (if applicable), and you'll be presented with something similar to the following:
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Make note of the SHA256 field.
SDK Setup
Now we're ready to add the Nearby SDK to your Android Studio project. Go to your Huawei Developer Console and click the HUAWEI AppGallery tile. Agree to the terms of use if prompted.
Click the "My projects" tile here. If you haven't already added your project to the AppGallery, add it now. You'll be asked for a project name. Make it something descriptive so you know what it's for.
Now, you should be on a screen that looks something like the following:
Click the "Add app" button. Here, you'll need to provide some details about your app, like its name and package name.
Once you click OK, some SDK setup instructions will be displayed. Follow them to get everything added to your project. You'll also need to add the following to the "dependencies" section of your app-level build.gradle file:
Code:
implementation 'com.huawei.hms:nearby:4.0.4.300'
If you ever need to come back to these instructions, you can always click the "Add SDK" button after "App information" on the "Project setting" page.
Now you should be back on the "Project setting" page. Find the "SHA-256 certificate fingerprint" field under "App information," click the "+" button, and paste your SHA-256.
Now, go to the Manage APIs tab on the "Project setting" page. Scroll down until you find "Nearby Service" and make sure it's enabled.
Now, if you're using obfuscation in your app, you'll need to whitelist a few things for HMS to work properly.
For ProGuard:
Code:
-ignorewarnings
-keepattributes *Annotation*
-keepattributes Exceptions
-keepattributes InnerClasses
-keepattributes Signature
-keepattributes SourceFile,LineNumberTable
-keep class com.hianalytics.android.**{*;}
-keep class com.huawei.updatesdk.**{*;}
-keep class com.huawei.hms.**{*;}
For AndResGuard:
Code:
"R.string.hms*",
"R.string.agc*",
"R.string.connect_server_fail_prompt_toast",
"R.string.getting_message_fail_prompt_toast",
"R.string.no_available_network_prompt_toast",
"R.string.third_app_*",
"R.string.upsdk_*",
"R.layout.hms*",
"R.layout.upsdk_*",
"R.drawable.upsdk*",
"R.color.upsdk*",
"R.dimen.upsdk*",
"R.style.upsdk*
That's it! The Nearby SDK should now be available in your project.
Basic Usage
There are currently three ways to use the Nearby SDK: Nearby Connection, Nearby Message, and Beacon Management.
Nearby Connection
Nearby Connection is the API that allows you to locally transmit and receive data to and from another device. The first thing you'll need to do to implement this is declare the use of quite a few permissions. These should go in your AndroidManifest.xml.
XML:
<!-- 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" />
<!-- If you don't care about devices running Android 10 or later, this can be replaced with 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" />
Some of these permissions, like "ACCESS_FINE_LOCATION" are "dangerous"-level permissions, so make sure you request access to them on Android Marshmallow and above.
Now, there are three usage "modes" for this API: Mesh, Star, and P2P.
Mesh allows all involved devices to send and receive data to and from all other involved devices. This comes at the cost of bandwidth speed, though, so this should only be used for things with low data throughput, like game-state sharing.
Star is similar to Mesh, but with a twist: the bandwidth is higher. What's the trade-off? Well, one device has to act as a "hub" of sorts. That hub can send to and receive from all other involved devices, but the rest can only communicate to the hub itself. This is useful for things like mass video and file sharing.
Finally, there's P2P. This is a simple 1-on-1 connection, where one device sends a file to another. This method has the highest bandwidth, but obviously the strictest constraints.
Below is an example of how to broadcast the ability to connect using the Star policy. This would be used on potential clients for the "hub" to connect to.
Code:
//Begin broadcasting the ability to receive connections.
fun startBroadcasting() {
val policy = Policy.POLICY_STAR
val option = BroadcastOption.Builder()
.setPolicy(policy)
.build()
val dataCallback = object : DataCallback() {
override fun onReceived(p0: String?, p1: Data?) {
//We'll get to the implementation of this later.
}
override fun onTransferUpdate(p0: String?, p1: TransferStateUpdate?) {
}
}
val connectionCallback = object : ConnectCallback() {
override fun onDisconnected(endpointId: String) {
//The connection has been terminated
}
override fun onEstablish(endpointId: String, connectInfo: ConnectInfo) {
//A connection has been established.
//This is where you either accept or reject the connection.
//Both parties need to accept the connection. You can either
//present a confirmation to the user or silently accept.
//Similarly to Bluetooth pairing, you can display an auth code
//on both devices. Obtain the auth code:
val authCode = connectInfo.authCode
//Silently accept:
Nearby.getDiscoveryEngine(context)
.acceptConnect(endpointId, dataCallback)
}
override fun onResult(endpointId: String, connectResult: ConnectResult) {
//Handle the result of a connection request.
when (connectResult.status.statusCode) {
StatusCode.STATUS_SUCCESS -> {
//The connection was accepted.
//We can start exchanging data.
}
StatusCode.STATUS_CONNECT_REJECTED -> {
//The connection was rejected.
//Notify the user.
}
else -> {
//This shouldn't happen...
}
}
}
}
Nearby.getDiscoveryEngine(context)
//"NAME" should be something descriptive (what is this device?)
//"SERVICE_ID" should be your app's package name
.startBroadcasting("NAME", "SERVICE_ID", connectionCallback, option)
.addOnSuccessListener {
//Broadcasting successfully started
}
.addOnFailureListener {
//Broadcasting failed to start
}
}
//This device is no longer accepting connections
fun stopBroadcasting() {
Nearby.getDiscoveryEngine(context)
.stopBroadcasting()
}
Next, a device needs to scan for potential connections. The following code shows how to do that.
Code:
//Start scanning for available connections.
fun startScanning() {
val policy = Policy.POLICY_STAR
val option = ScanOption.Builder()
.setPolicy(policy)
.build()
val scanEndpointCallback = object : ScanEndpointCallback() {
override fun onFound(endpointId: String?, endpointInfo: ScanEndpointInfo?) {
//A device has been found. Use this opportunity to either automatically
//connect to it, or add it to a list for the user to select from.
}
override fun onLost(endpointId: String?) {
//A device has gone out of range, been turned off, etc.
//It's no longer available to connect to, so if you're
//presenting a list, make sure to remove it.
}
}
Nearby.getDiscoveryEngine(context)
.startScan("SERVICE_ID", scanEndpointCallback, option)
.addOnSuccessListener {
//Scanning started successfully
}
.addOnFailureListener {
//Scanning couldn't start
}
}
//Stop scanning for new devices
fun stopScanning() {
Nearby.getDiscoveryEngine(context)
.stopScan()
}
Finally, once the scan is complete and the user (or your code) has selected a device, you'll need to initiate the connection:
Code:
//Connect to a device. This should be called
//from the scanner.
//endpointId comes from the onFound() method of
//the ScanEndpointCallback
fun startConnection(endpointId: String) {
val dataCallback = object : DataCallback() {
override fun onReceived(endpointId: String, data: Data) {
//We'll get to the implementation of this later.
}
override fun onTransferUpdate(endpointId: String, update: TransferStateUpdate) {
}
}
val connectionCallback = object : ConnectCallback() {
override fun onDisconnected(endpointId: String) {
//The connection has been terminated
}
override fun onEstablish(endpointId: String, connectInfo: ConnectInfo) {
//A connection has been established.
//This is where you either accept or reject the connection.
//Both parties need to accept the connection. You can either
//present a confirmation to the user or silently accept.
//Similarly to Bluetooth pairing, you can display an auth code
//on both devices. Obtain the auth code:
val authCode = connectInfo.authCode
//Silently accept:
Nearby.getDiscoveryEngine(context)
.acceptConnect(endpointId, dataCallback)
}
override fun onResult(endpointId: String, connectResult: ConnectResult) {
//Handle the result of a connection request.
when (connectResult.status.statusCode) {
StatusCode.STATUS_SUCCESS -> {
//The connection was accepted.
//We can start exchanging data.
}
StatusCode.STATUS_CONNECT_REJECTED -> {
//The connection was rejected.
//Notify the user.
}
else -> {
//This shouldn't happen...
}
}
}
}
Nearby.getDiscoveryEngine(context)
.requestConnect("NAME", endpointId, connectionCallback)
.addOnSuccessListener {
//Request was sent successfully
}
.addOnFailureListener {
//Request failed to send
}
}
//End a connection
fun stopConnection(endpointId: String) {
Nearby.getDiscoveryEngine(context)
.disconnect(endpointId)
}
Finally, once everything is connected, it's time to start transfering data. There are currently three forms of data transfer: bytes, files, and streams.
Bytes
Nearby Connection allows you to send small packets of data in the form of byte arrays. This could be useful for if you only need to send some simple data, like a chat message, or a game-state update. The size limit is 32KB.
To send a byte array, use the following code:
Code:
//Send some data to a client in the form
//of a byte array.
fun sendByteData(endpointId: String, data: ByteArray) {
Nearby.getTransferEngine(context)
.sendData(endpointId, Data.fromBytes(data))
}
Files
If you have a file you want to send (e.g. a video or music file), use this method.
Sending a file is similar to sending a byte array:
Code:
//Send some data to a client in the form
//of a File.
//Files received with this method are stored
//in the receiving device's Download folder.
fun sendFileData(endpointId: String, file: File) {
try {
Nearby.getTransferEngine(context)
.sendData(endpointId, Data.fromFile(file))
} catch (e: FileNotFoundException) {
//Handle accordingly
}
}
//Send some data to a client in the form
//of a File using ParcelFileDescriptor
//Files received with this method are stored
//in the receiving device's Download folder.
fun sendFileData(endpointId: String, file: ParcelFileDescriptor) {
try {
Nearby.getTransferEngine(context)
.sendData(endpointId, Data.fromFile(file))
} catch (e: FileNotFoundException) {
//Handle accordingly
}
}
Streams
If it's easier for you to send your data in the form of a stream,
you can also do that.
Yet again, the process is very similar to the previous two methods.
Code:
//Send some data to a client in the form
//of a stream.
fun sendStreamData(endpointId: String, stream: InputStream) {
Nearby.getTransferEngine(context)
.sendData(endpointId, Data.fromStream(stream))
}
//Send soe data to a client in the form
//of a stream using ParcelFileDescriptor.
fun sendStreamData(endpointId: String, stream: ParcelFileDescriptor) {
Nearby.getTransferEngine(context)
.sendData(endpointId, Data.fromStream(stream))
}
_______
If you want to cancel a transfer, it's also fairly simple:
Code:
//Cancel the transmission of data.
//The dataId can be obtained from the
//Data instance being sent.
fun cancelTransmission(dataId: Long) {
Nearby.getTransferEngine(context)
.cancelDataTransfer(dataId)
}
Now that you know how to send data, it's time to go over receiving it. In the code examples above for broadcasting and connecting, there's an unimplemented DataCallback. Well, it's time to implement it. Below is an example of how you might do that.
Code:
//An example implementation of a DataCallback
class DataReceiver : DataCallback() {
//A method to hold received data until we can
//properly retrieve files and streams.
private val receivedData = HashMap<Long, Data>()
override fun onReceived(endpointId: String, data: Data) {
//There's some new data.
when (data.type) {
Data.Type.BYTES -> {
//The data received is in the byte array format.
//Retrieve it as such, and handle accordingly.
//This is the only format where it's safe to retrieve the data here.
val bytes = data.asBytes()
//However, in this implementation, we're going to temporarily store
//the data reference and retrieve it the same way as we do files
//and streams.
receivedData[data.id] = data
}
Data.Type.FILE, Data.Type.STREAM -> {
//Temporarily store the data reference until the transfer is complete.
receivedData[data.id] = data
}
}
}
override fun onTransferUpdate(endpointId: String, update: TransferStateUpdate) {
when (update.status) {
TransferStateUpdate.Status.TRANSFER_STATE_SUCCESS -> {
//The transfer is complete. Retrieve data and handle it.
val data = receivedData[update.dataId]
when (data?.type) {
Data.Type.BYTES -> {
val bytes = data.asBytes()
}
Data.Type.FILE -> {
val file = data.asFile()
}
Data.Type.STREAM -> {
val stream = data.asStream()
}
}
}
TransferStateUpdate.Status.TRANSFER_STATE_CANCELED -> {
//The transfer was canceled
}
TransferStateUpdate.Status.TRANSFER_STATE_FAILURE -> {
//The transfer failed
}
TransferStateUpdate.Status.TRANSFER_STATE_IN_PROGRESS -> {
//The transfer is still in progress. You can use this event to display and
//update a progress indicator.
val progressPercent = (update.bytesTransferred.toFloat() / update.totalBytes.toFloat() * 100).toInt()
}
}
}
}
Nearby Message
Next up is the Nearby Message API. This is a way to publish and subscribe to messages generated by other devices or by dedicated "beacons."
The first thing to do is declare permissions. In order to properly use this API, the following permissions must be requested and granted:
XML:
<uses-permission android:name="android.permission.INTERNET " />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
Now, it's time to actually use it. First let's talk about publishing messages. Publishing and unpublishing is fairly simple:
Code:
//Publish a message for other devices to see.
//You can publish up to 50 messages within a
//5 second timespan.
//You can also specify a type string and/or
//a namespace string.
fun publishMessage(msg: String) {
Nearby.getMessageEngine(context)
.put(Message(msg.toByteArray()))
}
//Unpublish a message. The Message class
//takes care of checking message equality
//for you, so you only need to save the
//string itself.
//Make sure to call this in your component's
//onDestroy() method.
fun unpublishMessage(msg: String) {
Nearby.getMessageEngine(context)
.unput(Message(msg.toByteArray()))
}
Subscribing to messages is a bit more complex (obviously). There are two ways to subscribe: in the foreground, and in the background.
The following code is an example of how to subscribe in the foreground:
Code:
val handler = object : MessageHandler() {
override fun onFound(message: Message) {
//A new Message has been found.
}
override fun onLost(message: Message) {
//An existing message was lost.
}
//The following methods can be used to estimate the distance from a beacon
//or publisher, based on signal strength.
override fun onBleSignalChanged(message: Message, bleSignal: BleSignal) {
val strength = bleSignal.rssi
}
override fun onDistanceChanged(message: Message, distance: Distance) {
val dist = distance.meters
val precision = distance.precision
}
}
//Subscribe to new messages in the foreground.
//This should be run in a foreground Service or an Activity.
fun subscribeToMessagesForeground() {
val options = GetOption.Builder()
.setPicker(MessagePicker.Builder()
//MessagePicker.Builder has various options
//for filtering messages.
.build())
.setPolicy(com.huawei.hms.nearby.message.Policy.Builder()
//Policy.Builder has various options
//for filtering messages.
.build())
.build()
Nearby.getMessageEngine(context)
.get(handler, options)
}
Here's an example for subscribing in the background:
Code:
val intent = PendingIntent.getService(context, 0, Intent(context, MessageReceiver::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
//Subscribe to new messages in the background.
//To receive these messages, you'll need to set up
//an IntentService to handle a PendingIntent.
fun subscribeToMessagesBackground() {
val options = GetOption.Builder()
.setPicker(MessagePicker.Builder()
//MessagePicker.Builder has various options
//for filtering messages.
.build())
.setPolicy(com.huawei.hms.nearby.message.Policy.Builder()
//Policy.Builder has various options
//for filtering messages.
.build())
.build()
Nearby.getMessageEngine(context)
.get(intent, options)
}
//Usubscribe from new background messages.
//Make sure to call this on app exit.
fun unsubscribeFromMessagesBackground() {
Nearby.getMessageEngine(context)
.unget(intent)
}
class MessageReceiver : JobIntentService() {
override fun onHandleWork(intent: Intent) {
//Pass the data to the messaging API and let it
//call the appropriate callback methods.
Nearby.getMessageEngine(this)
.handleIntent(intent, object : MessageHandler() {
override fun onFound(message: Message) {
//A new Message has been found.
}
override fun onLost(message: Message) {
//An existing message was lost.
}
//The following methods can be used to estimate the distance from a beacon
//or publisher, based on signal strength.
override fun onBleSignalChanged(message: Message, bleSignal: BleSignal) {
val strength = bleSignal.rssi
}
override fun onDistanceChanged(message: Message, distance: Distance) {
val dist = distance.meters
val precision = distance.precision
}
})
}
}
Beacon Management
To set up beacons, please refer to Huawei's documentation.
Conclusion
And that's it! Be sure to check out Huawei's full documentation for more details.
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.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
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
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.
Code:
<!-- 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:
Code:
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:
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:
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.
Code:
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:
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:
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:
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:
Code:
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:
Code:
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:
Code:
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:
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:
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:
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:
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.
More articles like this, you can visit HUAWEI Developer Forum
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.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
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
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.
Code:
<!-- 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:
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:
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:
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.
Code:
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:
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:
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:
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:
Code:
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.
Note: The size of byte arrays cannot exceed 32 KB.
The following is the sample code for receiving byte arrays:
Code:
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:
Code:
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:
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:
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:
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:
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.
Introduction
Whether you are tracking down a weird behavior in your app or chasing a crash in app making the user frustrated, getting a precise and real time information is important. Huawei crash analytics is a primary crash reporting solution for mobile. It monitors and captures your crashes, intelligently analyses them, and then groups them into manageable issues. And it does this through lightweight SDK that won’t bloat your app. You can integrate Huawei crash analytics SDK with a single line of code before you publish.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
In this article, we will change app theme using Huawei Remote configuration and if something goes wrong while fetching data from remote config, we will report crash/exception using Huawei Crash Service.
To learn how to change app theme using Huawei Dark mode Awareness service, refer this.
Prerequisite
If you want to use Huawei Remote Configuration and Crash Service, you must have a developer account from AppGallery Connect. You need to create an application from your developer account and then integrate the HMS SDK into your project. I will not write these steps so that the article doesn’t lose its purpose and I will assume that it is already integrated in your project. You can find the guide from the link below.
HMS Integration Guide
Integration
1. Enable Remote Configuration and Crash Service in Manage APIs. Refer to Service Enabling.
2. Add AGC connect plugin in app-level build.gradle.
Code:
apply plugin: 'com.huawei.agconnect'
3. Integrate Crash Service and Remote configuration SDK by adding following code in app-level build.gradle.
Code:
implementation 'com.huawei.agconnect:agconnect-remoteconfig:1.5.2.300'
implementation 'com.huawei.agconnect:agconnect-crash:1.5.2.300'4.
4. Add following code in root-level build.gradle.
Code:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
// Configure the Maven repository address for the HMS Core SDK.
maven {url 'https://developer.huawei.com/repo/'}
}
dependencies {
classpath "com.android.tools.build:gradle:4.0.1"
// Add AppGallery Connect plugin configurations.
classpath 'com.huawei.agconnect:agcp:1.4.2.300'
}
}
allprojects {
repositories {
// Configure the Maven repository address for the HMS Core SDK.
maven {url 'https://developer.huawei.com/repo/'}
}
}
5. Declare the following permissions in Androidmanifest.xml
Code:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Development
We will define JSON which will have mode value as 0 or 1.
1. If the value of mode is 0, we will use system setting to change app theme. For example, if device has dark mode enabled in system setting, our app theme will be dark.
2. If the value of mode is 1, we will force our app to use day theme.
Code:
{
"jsonmode": [{
"mode": 0,
"details": "system_settings_mode"
}]
}
Open AGC, select your project. Choose Growing > Remote Config and enable Remote Config service. Once the remote config is enabled, define the key-value parameters.
Key : “mode_status”
Value : {
"jsonmode": [{
"mode": "0",
"details": "system_settings_mode"
}]
}
Note: mode value should be int, however we are intentionally adding value as String, so that our app throws JSONException which we can monitor on AGC dashboard.
Implementation
Let’s create instance of AGConnectConfig and add the default value to hashmap before connecting to remote config service.
Java:
private void initializeRemoteConfig() {
agConnectConfig = AGConnectConfig.getInstance();
Map<String, Object> map = new HashMap<>();
map.put("mode_status", "NA");
agConnectConfig.applyDefault(map);
}
To fetch parameter values from Remote Configuration.
Java:
agConnectConfig.fetch(5).addOnSuccessListener(new OnSuccessListener<ConfigValues>() {
@Override
public void onSuccess(ConfigValues configValues) {
agConnectConfig.apply(configValues);
String value = agConnectConfig.getValueAsString("mode_status");
Log.d(TAG, "remoteconfig value : " + value);
try {
int mode = parseMode(value);
Log.d(TAG, "mode value : " + mode);
if(mode == 0) {
initilizeDarkModeListner();
}
else if(mode == 1) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
} catch (JSONException e) {
Log.e(TAG,"JSONException : " +e.getMessage());
AGConnectCrash.getInstance().recordException(e);
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.e(TAG, " error: " + e.getMessage());
}
});
To parse the JSON received from Remote config.
Code:
private int parseMode(String json) throws JSONException {
if(json != null) {
JSONObject jsonObj = new JSONObject(json);
JSONArray jsonArrayMenu = jsonObj.getJSONArray("jsonmode");
for (int i = 0; i < jsonArrayMenu.length(); i++) {
JSONObject modeJsonObj = jsonArrayMenu.getJSONObject(i);
return modeJsonObj.getInt("mode");
}
}
return -1;
}
If parsing is successful, we will able to retrieve the mode value as 0 or 1.
However if parsing is unsuccessful, JSONException will be thrown and we will log this exception in AGC using Huawei Crash Service.
Java:
catch (JSONException e) {
Log.e(TAG,"JSONException : " +e.getMessage());
AGConnectCrash.getInstance().recordException(e);
}
Now when app encounters crash, Crash service reports the crash on dashboard in App Gallery connect. To monitor crash, as follows:
1. Sign in to App Gallery connect and select my project.
2. Choose the app.
3. Select Quality > Crash on left panel of the screen.
If you see parsing implementation of JSON, expected mode value should be integer
"mode": 0
But mistakenly, we have added mode value as string in remote config.
Code:
{
"jsonmode": [{
"mode": "0",
"details": "system_settings_mode"
}]
}
Now when we try to run our app, it will throw JSONException, since we are expecting mode value as int from remote config. This exception will be added to AGC dashboard using Huawei crash service.
As a developer, when I go to AGC dashboard to monito my app crash report, I realize my mistake and update the value in AGC remote config as follows:
Code:
{
"jsonmode": [{
"mode": 0,
"details": "system_settings_mode"
}]
}
Now our app will change its theme based on system settings whether if dark mode is enabled or not.
Code snippet of MainActivity.java
Java:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private AGConnectConfig agConnectConfig;
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initializeRemoteConfig();
ConfigValues last = agConnectConfig.loadLastFetched();
agConnectConfig.apply(last);
agConnectConfig.fetch(5).addOnSuccessListener(new OnSuccessListener<ConfigValues>() {
@Override
public void onSuccess(ConfigValues configValues) {
agConnectConfig.apply(configValues);
String value = agConnectConfig.getValueAsString("mode_status");
Log.d(TAG, "remoteconfig value : " + value);
try {
int mode = parseMode(value);
Log.d(TAG, "mode value : " + mode);
if(mode == 0)) {
initilizeDarkModeListner();
}
else if(mode == 1) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
} catch (JSONException e) {
Log.e(TAG,"JSONException : " +e.getMessage());
AGConnectCrash.getInstance().recordException(e);
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.e(TAG, " error: " + e.getMessage());
}
});
}
private void initializeRemoteConfig() {
agConnectConfig = AGConnectConfig.getInstance();
Map<String, Object> map = new HashMap<>();
map.put("mode_status", "NA");
agConnectConfig.applyDefault(map);
}
private void initilizeDarkModeListner() {
Awareness.getCaptureClient(this).getDarkModeStatus()
// Callback listener for execution success.
.addOnSuccessListener(new OnSuccessListener<DarkModeStatusResponse>() {
@Override
public void onSuccess(DarkModeStatusResponse darkModeStatusResponse) {
DarkModeStatus darkModeStatus = darkModeStatusResponse.getDarkModeStatus();
if (darkModeStatus.isDarkModeOn()) {
Log.i(TAG, "dark mode is on");
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
} else {
Log.i(TAG, "dark mode is off");
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
}
})
// Callback listener for execution failure.
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.e(TAG, "get darkMode status failed " + e.getMessage());
}
});
}
private int parseMode(String json) throws JSONException {
if(json != null) {
JSONObject jsonObj = new JSONObject(json);
JSONArray jsonArrayMenu = jsonObj.getJSONArray("jsonmode");
for (int i = 0; i < jsonArrayMenu.length(); i++) {
JSONObject modeJsonObj = jsonArrayMenu.getJSONObject(i);
return modeJsonObj.getInt("mode");
}
}
return -1;
}
}
Tips and Tricks
1. Huawei Crash services work on non-Huawei device.
2. AGConnectCrash.getInstance().testIt(mContext) triggers app crash. Make sure to comment or remove it before releasing your app.
3. Crash Service takes around 1 to 3 minutes to post the crash logs on App Gallery connect dashboard/console.
4. Crash SDK collects App and system data.
System data:
AAID, Android ID (obtained when AAID is empty), system type, system version, ROM version, device brand, system language, device model, whether the device is rooted, screen orientation, screen height, screen width, available memory space, available disk space, and network connection status.
App data:
APK name, app version, crashed stack, and thread stack.
5. The Crash SDK collects data locally and reports data to the collection server through HTTPS after encrypting the data.
Conclusion
In this article, we have learnt how Huawei crash service can help developers to monitor crash/exception report on AGC and fix it.
We uploaded wrong JSON data into Remote Configuration and cause our app to go into JSONException. Using Huawei Crash Service, we monitored the exception in AGC dashboard. After finding out issue in JSON data, we added correct data in remote config and fixed our app.
References
Huawei Crash Service
Huawei Remote Configuration
Original Source
Push messaging, with the proliferation of mobile Internet, has become a very effective way for mobile apps to achieve business success. It improves user engagement and stickiness by allowing developers to send messages to a wide range of users in a wide range of scenarios: taking the subway or bus, having a meal in a restaurant, having a chat... you name it. No matter what the scenario is, a push message is always a great helper for you to directly "talk" to your users, and for your users to know something informative.
Such great benefits brought by push messages, however, can be dampened by a challenge: the variety of mobile phone manufacturers. This is because usually each manufacturer has their own push messaging channels, which increases the difficulty for uniformly sending your app's push messages to mobile phones of different manufacturers. Of course there is an easy solution for this: sending your push messages to mobile phones of only one manufacturer, but this can limit your user base and prevent you from obtaining your desired messaging effects.
Then this well explains why we developers usually need to find a solution for our apps to be able to push their messages to devices of different brands.
I don't know about you, but the solution I found for my app is HMS Core Push Kit. Going on, I will demonstrate how I have integrated this kit and used its ability to aggregate third-party push messaging channels to implement push messaging on mobile phones made by different manufacturers, expecting greater user engagement and stickiness. Let's move on to the implementation.
PreparationsBefore integrating the SDK, make the following preparations:
1. Sign in to the push messaging platform of a specific manufacturer, create a project and app on the platform, and save the JSON key file of the project. (The requirements may vary depending on the manufacturer, so refer to the specific manufacturer's documentation to learn about their requirements.)
2. Configure app information in AppGallery Connet, but use the following build dependency instead when configuring the build dependencies:
Code:
dependencies {
implementation 'com.huawei.hms:push-fcm:6.3.0.304'
}
3. On the platform mentioned in the previous step, click My projects, find the app in the project, and go to Grow > Push Kit > Settings. On the page displayed, click Enable next to Configure other Android-based push, and then copy the key in the saved JSON key file and paste it in the Authentication parameters text box.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Development ProcedureNow, let's go through the development procedure.
1. Disable the automatic initialization of the SDK.
To do so, open the AndroidManifest.xml file, and add the <meta-data> element to the <application> element. Note that in the element, the name parameter has a fixed value of push_kit_auto_init_enabled. As for the value parameter, you can set it to false, indicating that the automatic initialization is disabled.
Code:
<manifest ...>
...
<application ...>
<meta-data
android:name="push_kit_auto_init_enabled"
android:value="false"/>
...
</application>
...
</manifest>
2. Initialize the push capability in either of the following ways:
Set value corresponding to push_kit_proxy_init_enabled in the <meta-data> element to true.
Code:
<application>
<meta-data
android:name="push_kit_proxy_init_enabled"
android:value="true" />
</application>
Explicitly call FcmPushProxy.init in the onCreate method of the Application class.
3. Call the getToken method to apply for a token.
Code:
private void getToken() {
// Create a thread.
new Thread() {
@Override
public void run() {
try {
// Obtain the app ID from the agconnect-services.json file.
String appId = "your APP_ID";
// Set tokenScope to HCM.
String tokenScope = "HCM";
String token = HmsInstanceId.getInstance(MainActivity.this).getToken(appId, tokenScope);
Log.i(TAG, "get token: " + token);
// Check whether the token is empty.
if(!TextUtils.isEmpty(token)) {
sendRegTokenToServer(token);
}
} catch (ApiException e) {
Log.e(TAG, "get token failed, " + e);
}
}
}.start();
}
private void sendRegTokenToServer(String token) {
Log.i(TAG, "sending token to server. token:" + token);
}
4. Override the onNewToken method.
After the SDK is integrated and initialized, the getToken method will not return a token. Instead, you'll need to obtain a token by using the onNewToken method.
Code:
@Override
public void onNewToken(String token, Bundle bundle) {
Log.i(TAG, "onSubjectToken called, token:" + token );
}
5. Override the onTokenError method.
This method will be called if the token fails to be obtained.
Code:
@Override
public void onTokenError(Exception e, Bundle bundle) {
int errCode = ((BaseException) e).getErrorCode();
String errInfo = e.getMessage();
Log.i(TAG, "onTokenError called, errCode:" + errCode + ",errInfo=" + errInfo );
}
6. Override the onMessageReceived method to receive data messages.
Code:
@Override
public void onMessageReceived(RemoteMessage message) {
Log.i(TAG, "onMessageReceived is called");
// Check whether the message is empty.
if (message == null) {
Log.e(TAG, "Received message entity is null!");
return;
}
// Obtain the message content.
Log.i(TAG, "get Data: " + message.getData()
+ "\n getFrom: " + message.getFrom()
+ "\n getTo: " + message.getTo()
+ "\n getMessageId: " + message.getMessageId()
+ "\n getSentTime: " + message.getSentTime()
+ "\n getDataMap: " + message.getDataOfMap()
+ "\n getMessageType: " + message.getMessageType()
+ "\n getTtl: " + message.getTtl()
+ "\n getToken: " + message.getToken());
Boolean judgeWhetherIn10s = false;
// Create a job to process a message if the message is not processed within 10 seconds.
if (judgeWhetherIn10s) {
startWorkManagerJob(message);
} else {
// Process the message within 10 seconds.
processWithin10s(message);
}
}
private void startWorkManagerJob(RemoteMessage message) {
Log.d(TAG, "Start new job processing.");
}
private void processWithin10s(RemoteMessage message) {
Log.d(TAG, "Processing now.");
}
7. Send downlink messages.
Currently, you can only use REST APIs on the server to send downlink messages through a third-party manufacturer's push messaging channel.
The following is the URL for calling the API using HTTPS POST:
Code:
POST https://push-api.cloud.huawei.com/v1/[appId]/messages:send
The request header looks like the following:
Code:
Content-Type: application/json; charset=UTF-8
Authorization: Bearer CF3Xl2XV6jMKZgqYSZFws9IPlgDvxqOfFSmrlmtkTRupbU2VklvhX9kC9JCnKVSDX2VrDgAPuzvNm3WccUIaDg==
An example of the notification message body is as follows:
Code:
{
"validate_only": false,
"message": {
"android": {
"notification": {
"title": "test title",
"body": "test body",
"click_action": {
"type": 3
}
}
},
"token": ["pushtoken1"]
}
}
And just like that, my app has got the ability to send its push messages to mobile phones of different manufacturers — without any other configurations. Easy-peasy, right?
ConclusionToday's highly developed mobile Internet has made push messaging an important and effective way for mobile apps to improve user engagement and stickiness. A great obstacle for push messaging to effectively play its role is the highly diversified mobile phone market that is inundated with various manufacturers.
In this article, I demonstrated my solution to aggregate the push channels of different manufacturers, which allowed my app to push messages in a unified way to devices made by those manufacturers. As proven, the whole implementation process is both straightforward and cost-effective, delivering a better messaging effect of push messages by ensuring that they can reach a bigger user base supported by various manufacturers.