Implementing HMS Nearby SDK into your App - Huawei Developers

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.

Related

Messaging Text Through HMS Nearby Connection

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

Huawei Cab Application (Login Module with HMS Account Kit and AGC Auth Service) Part1

More information like this, you can visit HUAWEI Developer Forum​
Introduction
Huawei Cab Application is to explore HMS Kits in real time scenario, use this app as reference to CP during HMS integration and understand easily about HMS function.
Login Module
The user can access Huawei Cab Application in Login page by HUAWEI ID or Email ID Login or Mobile Login and Google Sign In.
You can use AGC Auth Service to integrate one or more of the following authentication methods into your app for achieving easy and efficient user registration, and sign-in.
AGC Auth service is not providing user google mail id. So, you can retrieve your mail Id directly from third party google server by ID token, we have covered that one also in this article.
Huawei Cab Application needs user authentication to access application using following method:
Huawei Cab Authentication type:
1. Huawei Account Kit
2. Huawei Auth Service
App Screen
{
"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"
}
Huawei Account Kit
HUAWEI Account Kit provides developers with simple, secure, and quick sign-in, and authorization functions. Instead of entering accounts and passwords, and authorization waiting, users can click the Sign In with Huawei ID button to quickly and securely sign in to app.
HUAWEI Auth Service
Auth Service provides backend services and an SDK to authenticate users to your app. It supports multiple authentication providers such as Phone Number, Google Sign-In, Email ID and more, and report authentication credentials to the AppGallery Connect.
When a user signs in to an app again, the app can obtain the users personal information and other data protected by security rules in other server less functions from Auth Service.
Auth Service can greatly reduce your investment and costs in building an authentication system and its O&M.
Integration Preparations
To integrate HUAWEI Account Kit, you must complete the following preparations:
Create an app in AppGallery Connect.
Create a project in Android Studio.
Generate a signing certificate.
Generate a signing certificate fingerprint.
Configure the signing certificate fingerprint.
Add the app package name and save the configuration file.
Add the AppGallery Connect plug-in and the Maven repository in the project-level build.gradle file.
Configure the signature file in Android Studio.
Configuring the Development Environment
Enabling HUAWEI Account Kit and Auth Service
1. Sign in to AppGallery Connect, select My apps and click an App. Choose Develop > Overview > Manage APIs.
Enabling Authentication Modes
1. Sign in to AppGallery Connect, select My apps, and click your App. Choose Develop > Build > Auth Service. If it is the first time that you use Auth Service, click Enable now in the upper right corner.
2. Click Enable in the row of each authentication mode to be enabled. In this codelab, click Enable for Huawei account, Huawei game, and Anonymous account.
3. In the displayed dialog box, configure app information. Required information can be obtained as follows:
Huawei account: Obtain the app ID and secret by referring to Querying App Information.
Huawei game: Sign in to AppGallery Connect, select My apps, click your game. Choose Develop > Build > Google Sign In, and obtain the client id and client secret id.
Integrating the Account Kit and Auth Service SDK
If you are using Android Studio, you can integrate the App Linking SDK by using the Maven repository into your Android Studio project before development.
1. Sign in to AppGallery Connect, select My apps and click your App. Choose Develop > Overview.
2. Click agconnect-services.json to download the configuration file.
3. Copy agconnect-services.json file to the app's root directory.
4. Open the build.gradle file in the root directory of your Android Studio project.
5. Configure the following information in the build.gradle file:
Code:
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
google()
jcenter()
maven {url 'https://developer.huawei.com/repo/'}
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.huawei.agconnect:agcp:1.3.2.301'
}
}
6. Open the build.gradle file in the app directory.
7. Configure the HUAWEI Account Kit service address, Auth Service SDK address, and HUAWEI Game Service address.  
Code:
// Apply the APM plug-in.
apply plugin: 'com.huawei.agconnect'
dependencies {
implementation 'com.huawei.hms:hwid:4.0.1.300'
implementation 'com.huawei.agconnect:agconnect-auth:1.4.0.300'
implementation 'net.openid:appauth:0.7.1'
}
8. Click Sync Now to synchronize the configuration.
9. Add into your Manifest.
Code:
<activity android:name="net.openid.appauth.RedirectUriReceiverActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="Your package name"/>
</intent-filter>
</activity>
10. Add your app client Id from Google Developer Console for Google Sign In.
Code:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- android-client id-->
<string name="google_client_ids">Your Client ID</string>
<string name="redirect_uri">"your_package_name:/oauth2callback"</string>
</resources>
Code Snipped
Code:
private var mAuthManager: HuaweiIdAuthService? = null
private var mAuthParam: HuaweiIdAuthParams? = null
fun initHuaweiID() {
mAuthParam = HuaweiIdAuthParamsHelper(HuaweiIdAuthParams.DEFAULT_AUTH_REQUEST_PARAM)
.setEmail()
.setUid()
.setProfile()
.setMobileNumber()
.setIdToken()
.setAccessToken()
.createParams()
mAuthManager = HuaweiIdAuthManager.getService(this, mAuthParam)
}
fun onLoginButtonClick(view : View) {
if (!isConnected) {
toast("No network connectivity")
} else {
startActivityForResult(mAuthManager?.signInIntent, RC_SIGN_IN)
}
}
fun signInGoogle() {
val serviceConfiguration =
AuthorizationServiceConfiguration(
Uri.parse("https://accounts.google.com/o/oauth2/auth"), // authorization endpoint
Uri.parse("https://oauth2.googleapis.com/token")
)
val authorizationService = AuthorizationService(this)
val clientId = getString(R.string.google_client_ids)
val redirectUri = Uri.parse(getString(R.string.redirect_uri))
val builder = AuthorizationRequest.Builder(
serviceConfiguration,
clientId,
ResponseTypeValues.CODE,
redirectUri
)
builder.setScopes("openid email profile")
val request = builder.build()
val intent = authorizationService.getAuthorizationRequestIntent(request)
startActivityForResult(
intent,
RC_GOOGLE_SIGN_IN
)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == RC_SIGN_IN) {
//login success
//get user message by parseAuthResultFromIntent
val authHuaweiIdTask =
HuaweiIdAuthManager.parseAuthResultFromIntent(data)
if (authHuaweiIdTask.isSuccessful) {
val huaweiAccount = authHuaweiIdTask.result
Log.i(
TAG,
huaweiAccount.uid + " signIn success "
)
Log.i(
TAG,
"AccessToken: " + huaweiAccount.accessToken
)
displayInfo(huaweiAccount)
} else {
Log.i(
TAG,
"signIn failed: " + (authHuaweiIdTask.exception as ApiException).statusCode
)
}
} else if (requestCode == RC_GOOGLE_SIGN_IN) {
val response = AuthorizationResponse.fromIntent(data!!)
val error = AuthorizationException.fromIntent(data)
val authState =
AuthState(response, error)
if (response != null) {
Log.i(
TAG,
String.format("Handled Authorization Response %s ", authState.toString())
)
val service = AuthorizationService(this)
service.performTokenRequest(
response.createTokenExchangeRequest()
) { tokenResponse, exception ->
if (exception != null) {
Log.w(
TAG,
"Token Exchange failed",
exception
)
} else {
if (tokenResponse != null) {
val suffixUrl = "tokeninfo?id_token=${tokenResponse.idToken}"
viewModel.getGmailMailID(suffixUrl)
viewModel.googleMailResponse.observe([email protected], Observer {
if (it != null) {
if (it.error.toString().contentEquals("invalid_token")) {
Toast.makeText([email protected], "${it.error_description}", Toast.LENGTH_LONG).show()
} else{
GoogleMailID = "${it.email}"
Log.d("GoogleMail_ID ", "${it.email}")
tokenResponse.idToken?.let { agcAuthWithGoogle(it) }
}
} else {
Toast.makeText([email protected], "Somethings error. Please try again later", Toast.LENGTH_LONG).show()
}
})
}
}
}
}
}
else {
mCallbackManager!!.onActivityResult(requestCode, resultCode, data)
}
}
fun getGmailMailID(url: String) {
val googleUserModel: GoogleUserModel
viewModelScope.launch {
try {
MyGoogleApi
.invoke()
.getGoogleMailID(url)
.enqueue(object : Callback<GoogleUserModel> {
override fun onFailure(call: Call<GoogleUserModel>, t: Throwable) {
Log.e(TAG, "onFailure: "+t.localizedMessage )
googleMailResponse.value = null
}
override fun onResponse(
call: Call<GoogleUserModel>,
response: Response<GoogleUserModel>
) {
if (response.isSuccessful) {
googleMailResponse.value = response.body()
} else {
googleMailResponse.value = null
}
}
})
} catch (e: Exception) {
e.stackTrace
}
}
}
private fun createAccount(email: String, password: String) {
val settings = VerifyCodeSettings.newBuilder()
.action(VerifyCodeSettings.ACTION_REGISTER_LOGIN) //ACTION_REGISTER_LOGIN/ACTION_RESET_PASSWORD
.sendInterval(30) // Minimum sending interval, ranging from 30s to 120s.
.locale(Locale.getDefault()) // Language in which a verification code is sent, which is optional. The default value is Locale.getDefault.
.build()
val task =
EmailAuthProvider.requestVerifyCode(email, settings)
task.addOnSuccessListener(
TaskExecutors.uiThread(),
OnSuccessListener {
Log.d("Email Auth", " Success")
val inflater = layoutInflater
val alertLayout: View =
inflater.inflate(R.layout.dialog_verification_code, null)
val verifyBtn =
alertLayout.findViewById<Button>(R.id.verifyBtn)
val edtverifyBtn = alertLayout.findViewById<EditText>(R.id.smsCodeEt)
val alert =
AlertDialog.Builder(this)
alert.setTitle("Verifying code")
// this is set the view from XML inside AlertDialog
alert.setView(alertLayout)
// disallow cancel of AlertDialog on click of back button and outside touch
alert.setCancelable(false)
val dialog = alert.create()
verifyBtn.setOnClickListener {
if (!edtverifyBtn.text.toString().isEmpty()) {
dialog.dismiss()
verifyEmailWithCode(
edtverifyBtn.text.toString(),
email,
password
)
} else {
Toast.makeText(
[email protected],
"Email Code must not be empty!",
Toast.LENGTH_LONG
).show()
}
}
dialog.show()
}).addOnFailureListener(
TaskExecutors.uiThread(),
OnFailureListener { e -> Log.d("Email Auth", " Failed " + e.message.toString()) })
}
private fun verifyEmailWithCode(
code: String,
email: String,
password: String
) {
val emailUser = EmailUser.Builder()
.setEmail(email)
.setVerifyCode(code)
.setPassword(password) // Optional. If this parameter is set, the current user has created a password and can use the password to sign in.
// If this parameter is not set, the user can only sign in using a verification code.
.build()
viewModel.AGCCreateUser_EmailVerificationCode(emailUser)
}
private fun startPhoneNumberVerification(phoneNumber: String) {
val settings = VerifyCodeSettings.newBuilder()
.action(VerifyCodeSettings.ACTION_REGISTER_LOGIN) //ACTION_REGISTER_LOGIN/ACTION_RESET_PASSWORD
.sendInterval(30) // Minimum sending interval, which ranges from 30s to 120s.
.locale(Locale.getDefault()) // Optional. It indicates the language for sending a verification code. The value of locale must contain the language and country/region information. The defualt value is Locale.getDefault.
.build()
PhoneAuthProvider.verifyPhoneCode(
"91", // Country Code
phoneNumber, // Phone Num
settings,
object : VerifyCodeSettings.OnVerifyCodeCallBack {
override fun onVerifySuccess(
shortestInterval: String,
validityPeriod: String
) {
Log.d("Phone Auth", " Success")
}
override fun onVerifyFailure(e: Exception) {
Log.d("Phone Auth", " Failed " + e.message.toString())
}
})
}
private fun verifyPhoneNumberWithCode(
code: String,
phoneNumber: String
) {
val phoneUser = PhoneUser.Builder()
.setCountryCode("+91")
.setPhoneNumber(phoneNumber) // The value of phoneNumber must contains the country/region code and mobile number.
.setVerifyCode(code)
.setPassword("Your password") // Mandatory. If this parameter is set, a password has been created for the current user by default and the user can sign in using the password.
// Otherwise, the user can only sign in using a verification code.
.build()
viewModel.AGCCreateUser_VerificationCode(phoneUser)
}
private fun signin_withverificationcode() {
val credential = PhoneAuthProvider.credentialWithVerifyCode(
"+91",
field_phone_number!!.text.toString(),
"Your password",
field_verification_code1!!.text.toString()
)
viewModel.AGCGoogleSignIn(credential)
}
private fun validatePhoneNumber(): Boolean {
val phoneNumber = field_phone_number!!.text.toString()
if (TextUtils.isEmpty(phoneNumber)) {
field_phone_number!!.error = "Invalid phone number."
return false
}
return true
}
Video Demo for Login with Mobile No
Reference URL:
Account Kit: https://developer.huawei.com/consumer/en/doc/development/HMS-Guides/account-introduction-v4
AGC Auth Service: https://developer.huawei.com/consumer/en/doc/development/AppGallery-connect-Guides/agc-auth-service-introduction
Hi, how do I verify an anonymous account?

Improving app security with HMS Safety Detect

{
"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"
}
These days mobile devices are part of our life. We do many operations from our mobile phones such as making payment, logging in to social media accounts, checking our bank accounts.
These are the operations which need high security level. If our device will have malicious apps or something like that, our accounts will have trouble and we may suffer many financial and moral damages.
In this article, I will talk about how to improve app security by using HMS Safety Detect Kit.
To do that, I have developed a simple secure web browser app. Because in web browsers, we can use bank websites, we can login to our social media, we can make some payment and use our credit/bank card information. We wouldn’t like to our information to be stolen.
App Preparations
I use Koin framework for dependency injection in my application.
To use Koin Framework in our application, we should add 3 dependencies to our app. In the above, you can find dependencies which you need to add in app-level build.gradle file.
Code:
def koinVersion = "2.2.0-rc-4"
dependencies {
....
// Koin for Android
implementation "org.koin:koin-android:$koinVersion"
// Koin Android Scope feature
implementation "org.koin:koin-android-scope:$koinVersion"
// Koin Android ViewModel feature
implementation "org.koin:koin-android-viewmodel:$koinVersion"
}
After we have implemented the Koin dependencies, we need to create our modules which we will add in our application class.
We will get necessary objects with the help of these modules. I prefer to define different module files for different works.
Code:
val applicationModule = module {
single(named("appContext")){ androidApplication().applicationContext }
factory { HmsHelper() }
factory { SystemHelper() }
}
Code:
val dataModule = module {
factory<ErrorItem>(named("HmsNotAvailable")) { ErrorItem(
icon = ContextCompat.getDrawable(get(named("appContext")), R.drawable.huawei)!!,
title = androidContext().getString(R.string.hms_not_available),
message = androidContext().getString(R.string.download_hms_core)) }
factory<ErrorItem>(named("DeviceNotSecure")) { ErrorItem(
icon = ContextCompat.getDrawable(get(named("appContext")), R.drawable.ic_device_not_secure)!!,
title = androidContext().getString(R.string.device_not_secure),
message = androidContext().getString(R.string.device_not_secure_message)) }
factory<ErrorItem>(named("MaliciousApps")) { ErrorItem(
icon = ContextCompat.getDrawable(get(named("appContext")), R.drawable.ic_malicious_apps)!!,
title = androidContext().getString(R.string.device_not_secure),
message = androidContext().getString(R.string.malicious_apps_message)) }
}
Code:
val viewModelModule = module {
viewModel { SplashViewModel() }
}
After we have defined our modules, we need to setup Koin in our application class.
While starting Koin, we should add our modules which we have defined above, and if we want to use app context, we should androidContext value.
XML:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.berkberber.hms_securewebbrowser">
....
<application
android:name=".SecureWebBrowserApp"
....
>
....
</application>
</manifest>
Code:
class SecureWebBrowserApp: Application(){
override fun onCreate() {
super.onCreate()
setup()
}
private fun setupKoin(){
startKoin {
androidContext([email protected])
modules(
applicationModule,
viewModelModule,
dataModule
)
}
}
private fun setup(){
setupKoin()
}
}
To get more information about app and to see how I used other things such as Navigation Component, MVVM, and etc. you can visit my GitHub repository.
HMS Safety Detect
Safety Detect Kit helps us to improve the security level of our apps. There are 5 different APIs we can use with HMS Safety Detect Kit.
SysIntegrity API: Helps us to check device security. We can determine that device has been rooted or has not.
AppsCheck API: Helps us to determine and list malicious apps which have installed to device.
URLCheck API: Helps us check whether websites are safe.
UserDetect API: Helps us to determine that user is fake or is not.
WifiDetect API: Helps us to check whether Wi-Fi which the device has connected is secure.
Note: UserDetect API is available outside of Chinese mainland. WifiDetect API is available only in the Chinese mainland.
In this article, I have been focused on app security. So, I used SysIntegrity API and AppsCheck API and I will give you informations about these APIs.
Checking is HMS available on device (optional)
We will use Safety Detect Kit in our application. Safety Detect Kit requires HMS Core to be installed on the device.
We don’t have to make this control, but if device doesn’t have HMS, we can’t use HMS Safety Detect Kit. That’s why I recommend you to check HMS Core availability on device and if device doesn’t have HMS, it is better to show an error screen to user.
To check HMS availability we need to add base HMS dependency to our app-level build.gradle file.
To check that device has HMS support or has not, we can write very basic function called as isHmsAvailable().
Code:
def hmsBaseVersion = "5.0.3.300"
dependencies {
...
// HMS Base
implementation "com.huawei.hms:base:${hmsBaseVersion}"
}
Code:
class HmsHelper: KoinComponent{
private val appContext: Context by inject(named("appContext"))
fun isHmsAvailable(): Boolean {
val isAvailable = HuaweiApiAvailability.getInstance().isHuaweiMobileNoticeAvailable(appContext)
return (ConnectionResult.SUCCESS == isAvailable)
}
}
If this function returns true, that means device has HMS support and we can start our application.
If this function returns false, that means device doesn’t have HMS support and we shouldn’t start our application. We may show an error screen to user.
SysIntegrity API
SysIntegrity API helps us to check that the user’s device is secure or is not. Even if the device has been rooted, SysIntegrity API will tell us that device is not secure.
To check the device security, we can call our isDeviceSecure() function.
As you see, this function will create a nonce value with an algorithm and pass this value to checkDeviceSecurity() function.
You may ask that, what is the algorithm value which I have used as “Constants.SAFETY_DETECT_ALGORITHM”. You can define this algorithm value as shown in below:
Code:
object Constants{
const val BASIC_INTEGRITY = "basicIntegrity"
const val SAFETY_DETECT_ALGORITHM = "SHA1PRNG"
}
As you see, we have defined two different values. We will use these values while checking device security.
You already know where to use SAFETY_DETECT_ALGORITHM value.
We will use BASIC_INTEGRITY value to get device security situation from JSON.
If this value returns true, that means user’s device is secure.
If this value returns false, that means device is not secure or device has been rooted.
Code:
object SafetyDetectService : KoinComponent {
private val appContext: Context by inject(named("appContext"))
private val client: SafetyDetectClient = SafetyDetect.getClient(appContext)
fun isDeviceSecure(serviceListener: IServiceListener<Boolean>) {
val nonce = ByteArray(24)
try {
val random: SecureRandom = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
SecureRandom.getInstanceStrong()
else
SecureRandom.getInstance(Constants.SAFETY_DETECT_ALGORITHM)
random.nextBytes(nonce)
} catch (error: NoSuchAlgorithmException) {
serviceListener.onError(ErrorType.NO_SUCH_OBJECT)
}
checkDeviceSecurity(nonce, serviceListener)
}
private fun checkDeviceSecurity(nonce: ByteArray, serviceListener: IServiceListener<Boolean>){
client.sysIntegrity(nonce, BuildConfig.APP_ID)
.addOnSuccessListener { sysIntegrityResp ->
SafetyDetectHelper.getPayloadDetailAsJson(sysIntegrityResp)?.let { jsonObject ->
serviceListener.onSuccess(jsonObject.getBoolean(Constants.BASIC_INTEGRITY))
} ?: kotlin.run {
serviceListener.onError(ErrorType.SERVICE_FAILURE)
}
}
.addOnFailureListener {
serviceListener.onError(ErrorType.SERVICE_FAILURE)
}
}
}
As I talked about above, we need to get a json object from SysIntegrityResp object which has been returned by SysIntegrity API. To get this value, we can define a helper object and we can add all operations about getting json in here.
As you see in the below, we will send a SysIntegrityResp object as parameter and with the help of this function, we can get json object about our device security.
Code:
object SafetyDetectHelper {
fun getPayloadDetailAsJson(sysIntegrityResp: SysIntegrityResp): JSONObject? {
val jwsStr = sysIntegrityResp.result
val jwsSplit = jwsStr.split(".").toTypedArray()
val jwsPayloadStr = jwsSplit[1]
val payloadDetail = String(Base64.decode(
jwsPayloadStr.toByteArray(StandardCharsets.UTF_8), Base64.URL_SAFE),
StandardCharsets.UTF_8)
return try {
JSONObject(payloadDetail)
}catch (jsonError: JSONException){
null
}
}
}
If device is secure, we can do our next operations which we need to do. If device is not secure, we should show an error screen to user and we shouldn’t let user to start our application.
AppsCheck API
AppsCheck API helps us to determine malicious apps in user’s device. Thus, if device has some malicious apps, we will not let user to start our application for user’s security.
getMaliciousAppsList() function gives us a list of malicious app and it uses MaliciousAppsData class which has been defined by Huawei as a model class.
This API will return us a response object and this object will have the malicious apps list. If there is not any malicious apps on the device, we can return null and let user to use our application.
But if there are some malicious apps, we shouldn’t let user to start our application and we can show an error screen to user. If we would like to we can list malicious apps to user.
Note: It is better to list malicious apps and let user to delete these applications from device. That is what I am doing in my app. Also, if we would like to do more operations about malicious apps, we can define our own class like I talked about below.
Code:
object SafetyDetectService : KoinComponent {
private val appContext: Context by inject(named("appContext"))
private val client: SafetyDetectClient = SafetyDetect.getClient(appContext)
fun checkMaliciousApps(serviceListener: IServiceListener<ArrayList<MaliciousApps>?>){
client.maliciousAppsList
.addOnSuccessListener { maliciousAppsListResp ->
if(maliciousAppsListResp.rtnCode == CommonCode.OK){
val maliciousAppsList: List<MaliciousAppsData> = maliciousAppsListResp.maliciousAppsList
if(maliciousAppsList.isEmpty())
serviceListener.onSuccess(null)
else{
var maliciousApps = arrayListOf<MaliciousApps>()
for(maliciousApp in maliciousAppsList){
maliciousApp.apply {
maliciousApps.add(MaliciousApps(packageName = apkPackageName,
sha256 = apkSha256,
apkCategory = apkCategory))
}
}
serviceListener.onSuccess(maliciousApps)
}
}
}
.addOnFailureListener {
serviceListener.onError(ErrorType.SERVICE_FAILURE)
}
}
}
If we would like to do more operations like getting app icon, app name and etc. we can define our own data class.
I defined my own data class as shown in the below to do more specific operations with malicious apps.
Code:
data class MaliciousApps(
val packageName: String,
val sha256: String,
val apkCategory: Int
): KoinComponent{
private val appContext: Context = get(named("appContext"))
private val systemHelper: SystemHelper = get()
fun getAppIcon(): Drawable = systemHelper.getAppIconByPackageName(packageName)
fun getAppName(): String = systemHelper.getAppNameByPackageName(packageName)
fun getThreatDescription(): String {
return when(apkCategory){
1 -> appContext.getString(R.string.risky_app_description)
2 -> appContext.getString(R.string.virus_app_description)
else -> ""
}
}
}
Here I am just using same values with Huawei’s MaliciousAppsData class. But I added my own functions in here to get app icon, app name and threat description.
To get more information about application by package name, we can define new object called as SystemHelper and we can do these operations in here.
Code:
class SystemHelper: KoinComponent {
private val appContext: Context by inject(named("appContext"))
/**
* Getting application information by package name
* @param packageName: Package name of the app that we want to get information about
* @return ApplicationInfo class to get app icons, app names and etc. by package name
*/
private fun getAppByPackageName(packageName: String): ApplicationInfo{
return appContext.packageManager.getApplicationInfo(packageName, 0)
}
/**
* Getting application icon by package name
* @param packageName: Package name of the app which we want to get icon
* @return Icon of the application as drawable
*/
fun getAppIconByPackageName(packageName: String): Drawable{
val app = getAppByPackageName(packageName)
return appContext.packageManager.getApplicationIcon(app)
}
/**
* Getting application name by package name
* @param packageName: Package name of the app which we want to get name
* @return Name of the application as drawable
*/
fun getAppNameByPackageName(packageName: String): String{
val app = getAppByPackageName(packageName)
return appContext.packageManager.getApplicationLabel(app).toString()
}
}
When API founds malicious apps we need to list these apps to user and let user to delete these apps from device.
To do that, we can use selectedApp() function. This function will take the malicious app and ask user to delete them.
We need to detect that user has accepted to deleting application or has not. We need to start activity with result and we need to listen this result. If user really delete the application, we need to remove it from list. If there is not any malicious app on list after removing it, we can navigate user to our app.
Code:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(requestCode == DELETE_REQUEST_CODE){
when(resultCode){
Activity.RESULT_OK -> {
maliciousApps.remove(selectedMaliciousApp)
setRecyclerView()
}
Activity.RESULT_CANCELED -> {
Toast.makeText(requireContext(), requireContext().getString(R.string.should_delete_app), Toast.LENGTH_LONG).show()
}
}
}
}
private var deleteClickListener = object: DeleteClickListener{
override fun selectedApp(maliciousApp: MaliciousApps) {
var deleteIntent = Intent(Intent.ACTION_DELETE).apply {
data = Uri.parse("package:${maliciousApp.packageName}")
putExtra(Intent.EXTRA_RETURN_RESULT, true)
}
startActivityForResult(deleteIntent, DELETE_REQUEST_CODE)
}
}
To learn more about the app and examine it, you can visit my GitHub repository.
References
berkberberr/HMS-SecureWebBrowserExample: This repository is a secure web browser app which is using Huawei Mobile Services. (github.com)
Safety Detect: SysIntegrity, URLCheck, AppsCheck, UserDetect - HUAWEI Developer

Integration of Huawei Push Kit in Book Reading Android app (Kotlin) - Part 3

{
"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"
}
Introduction
In this article, we can learn how to integrate the Huawei Push Kit in Book Reading app to send the push message notification to users phone from the AppGallery Connect. Push notifications offers a great way to increase your application’s user engagement and boost your retention rates by sending meaningful messages or by informing users about your application. These messages can be sent at any time and even if your app is not running at that time. So, I will provide the series of articles on this Book Reading App, in upcoming articles I will integrate other Huawei Kits.
Push Kit
Huawei Push Kit is a messaging service developed by Huawei for developers to send messages to apps on users’ device in real time. Push Kit supports two types of messages: notification messages and data messages. You can send notifications and data messages to your users from your server using the Push Kit APIs or directly from the AppGallery Push Kit Console.
AppGallery Connect
Find the Push Kit message service in AppGallery connect dashboard.
Choose My Projects > Grow > Push Kit, and click Enable now.
Follow the steps to send the notification message to device from AppGallery Connect, Sending a Notification Message.
Requirements
1. Any operating system (MacOS, Linux and Windows).
2. Must have a Huawei phone with HMS 4.0.0.300 or later.
3. Must have a laptop or desktop with Android Studio, Jdk 1.8, SDK platform 26 and Gradle 4.6 and above installed.
4. Minimum API Level 24 is required.
5. Required EMUI 9.0.0 and later version devices.
How to integrate HMS Dependencies
1. First register as Huawei developer and complete identity verification in Huawei developers website, refer to register a Huawei ID.
2. Create a project in android studio, refer Creating an Android Studio Project.
3. Generate a SHA-256 certificate fingerprint.
4. To generate SHA-256 certificate fingerprint. On right-upper corner of android project click Gradle, choose Project Name > Tasks > android, and then click signingReport, as follows.
Note: Project Name depends on the user created name.
5. Create an App in AppGallery Connect.
6. Download the agconnect-services.json file from App information, copy and paste in android Project under app directory, as follows.
7. Enter SHA-256 certificate fingerprint and click Save button, as follows.
Note: Above steps from Step 1 to 7 is common for all Huawei Kits.
8. Click Manage APIs tab and enable Push Kit.
9. Add the below maven URL in build.gradle(Project) file under the repositories of buildscript, dependencies and allprojects, refer Add Configuration.
Code:
maven { url 'http://developer.huawei.com/repo/' }
classpath 'com.huawei.agconnect:agcp:1.6.0.300'
10. Add the below plugin and dependencies in build.gradle(Module) file.
Java:
apply plugin: id 'com.huawei.agconnect'
// Huawei AGC
implementation 'com.huawei.agconnect:agconnect-core:1.6.0.300'
// Huawei Push Kit
implementation 'com.huawei.hms:push:6.3.0.302'
// PDF Viewer
implementation 'com.github.barteksc:android-pdf-viewer:2.8.2'
11. Now Sync the gradle.
12. Add the required permission to the AndroidManifest.xml file.
XML:
// Push Kit
<uses-permission android:name="android.permission.INTERNET" />
<service
android:name=".PushService"
android:exported="false">
<intent-filter>
<action android:name="com.huawei.push.action.MESSAGING_EVENT" />
</intent-filter>
</service>
Let us move to development
I have created a project on Android studio with empty activity let us start coding.
In the WebViewActivity.kt to find the web view of pdf document.
Java:
class WebViewActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_web_view)
webView.webViewClient = WebViewClient()
webView.settings.setSupportZoom(true)
webView.settings.javaScriptEnabled = true
val url = getPdfUrl() webView.loadUrl("https://docs.google.com/gview?embedded=true&url=$url")
}
companion object{
fun getPdfUrl(): String {
return "https://mindorks.s3.ap-south-1.amazonaws.com/courses/MindOrks_Android_Online_Professional_Course-Syllabus.pdf"
}
}
}
Create PushService.kt class to send the push notification to device.
Code:
class PushService : HmsMessageService() {
// When an app calls the getToken method to apply for a token from the server,
// if the server does not return the token during current method calling, the server can return the token through this method later.
// This method callback must be completed in 10 seconds. Otherwise, you need to start a new Job for callback processing.
// @param token token
override fun onNewToken(token: String?) {
Log.i(TAG, "received refresh token:$token")
// send the token to your app server.
if (!token.isNullOrEmpty()) {
// This method callback must be completed in 10 seconds. Otherwise, you need to start a new Job for callback processing.
refreshedTokenToServer(token)
}
val intent = Intent()
intent.action = CODELABS_ACTION
intent.putExtra("method", "onNewToken")
intent.putExtra("msg", "onNewToken called, token: $token")
sendBroadcast(intent)
}
private fun refreshedTokenToServer(token: String) {
Log.i(TAG, "sending token to server. token:$token")
}
// This method is used to receive downstream data messages.
// This method callback must be completed in 10 seconds. Otherwise, you need to start a new Job for callback processing.
// @param message RemoteMessage
override fun onMessageReceived(message: RemoteMessage?) {
Log.i(TAG, "onMessageReceived is called")
if (message == null) {
Log.e(TAG, "Received message entity is null!")
return
}
// getCollapseKey() Obtains the classification identifier (collapse key) of a message.
// getData() Obtains valid content data of a message.
// getMessageId() Obtains the ID of a message.
// getMessageType() Obtains the type of a message.
// getNotification() Obtains the notification data instance from a message.
// getOriginalUrgency() Obtains the original priority of a message.
// getSentTime() Obtains the time when a message is sent from the server.
// getTo() Obtains the recipient of a message.
Log.i(TAG, """getCollapseKey: ${message.collapseKey}
getData: ${message.data}
getFrom: ${message.from}
getTo: ${message.to}
getMessageId: ${message.messageId}
getMessageType: ${message.messageType}
getSendTime: ${message.sentTime}
getTtl: ${message.ttl}
getSendMode: ${message.sendMode}
getReceiptMode: ${message.receiptMode}
getOriginalUrgency: ${message.originalUrgency}
getUrgency: ${message.urgency}
getToken: ${message.token}""".trimIndent())
// getBody() Obtains the displayed content of a message
// getTitle() Obtains the title of a message
// getTitleLocalizationKey() Obtains the key of the displayed title of a notification message
// getTitleLocalizationArgs() Obtains variable parameters of the displayed title of a message
// getBodyLocalizationKey() Obtains the key of the displayed content of a message
// getBodyLocalizationArgs() Obtains variable parameters of the displayed content of a message
// getIcon() Obtains icons from a message
// getSound() Obtains the sound from a message
// getTag() Obtains the tag from a message for message overwriting
// getColor() Obtains the colors of icons in a message
// getClickAction() Obtains actions triggered by message tapping
// getChannelId() Obtains IDs of channels that support the display of messages
// getImageUrl() Obtains the image URL from a message
// getLink() Obtains the URL to be accessed from a message
// getNotifyId() Obtains the unique ID of a message
val notification = message.notification
if (notification != null) {
Log.i(TAG, """
getTitle: ${notification.title}
getTitleLocalizationKey: ${notification.titleLocalizationKey}
getTitleLocalizationArgs: ${Arrays.toString(notification.titleLocalizationArgs)}
getBody: ${notification.body}
getBodyLocalizationKey: ${notification.bodyLocalizationKey}
getBodyLocalizationArgs: ${Arrays.toString(notification.bodyLocalizationArgs)}
getIcon: ${notification.icon}
getImageUrl: ${notification.imageUrl}
getSound: ${notification.sound}
getTag: ${notification.tag}
getColor: ${notification.color}
getClickAction: ${notification.clickAction}
getIntentUri: ${notification.intentUri}
getChannelId: ${notification.channelId}
getLink: ${notification.link}
getNotifyId: ${notification.notifyId}
isDefaultLight: ${notification.isDefaultLight}
isDefaultSound: ${notification.isDefaultSound}
isDefaultVibrate: ${notification.isDefaultVibrate}
getWhen: ${notification.`when`}
getLightSettings: ${Arrays.toString(notification.lightSettings)}
isLocalOnly: ${notification.isLocalOnly}
getBadgeNumber: ${notification.badgeNumber}
isAutoCancel: ${notification.isAutoCancel}
getImportance: ${notification.importance}
getTicker: ${notification.ticker}
getVibrateConfig: ${notification.vibrateConfig}
getVisibility: ${notification.visibility}""".trimIndent())
showNotification(notification.title,notification.body)
}
val intent = Intent()
intent.action = CODELABS_ACTION
intent.putExtra("method", "onMessageReceived")
intent.putExtra("msg", "onMessageReceived called, message id:" + message.messageId + ", payload data:" + message.data)
sendBroadcast(intent)
val judgeWhetherIn10s = false
// If the messages are not processed in 10 seconds, the app needs to use WorkManager for processing.
if (judgeWhetherIn10s) {
startWorkManagerJob(message)
} else {
// Process message within 10s
processWithin10s(message)
}
}
private fun showNotification(title: String?, body: String?) {
val intent = Intent(this, WebViewActivity::class.java)
intent.putExtra("URL", "Provide link here")
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_ONE_SHOT)
val soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val notificationBuilder = NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.sym_def_app_icon)
.setContentTitle(title)
.setContentText(body)
.setAutoCancel(true)
.setSound(soundUri)
.setContentIntent(pendingIntent)
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(0, notificationBuilder.build())
}
private fun startWorkManagerJob(message: RemoteMessage?) {
Log.d(TAG, "Start new Job processing.")
}
private fun processWithin10s(message: RemoteMessage?) {
Log.d(TAG, "Processing now.")
}
override fun onMessageSent(msgId: String?) {
Log.i(TAG, "onMessageSent called, Message id:$msgId")
val intent = Intent()
intent.action = CODELABS_ACTION
intent.putExtra("method", "onMessageSent")
intent.putExtra("msg", "onMessageSent called, Message id:$msgId")
sendBroadcast(intent)
}
override fun onSendError(msgId: String?, exception: Exception?) {
Log.i(TAG, "onSendError called, message id:$msgId, ErrCode:${(exception as SendException).errorCode}, " +
"description:${exception.message}")
val intent = Intent()
intent.action = CODELABS_ACTION
intent.putExtra("method", "onSendError")
intent.putExtra("msg", "onSendError called, message id:$msgId, ErrCode:${exception.errorCode}, " +
"description:${exception.message}")
sendBroadcast(intent)
}
override fun onTokenError(e: Exception) {
super.onTokenError(e)
}
private fun getToken() {
showLog("getToken:begin")
object : Thread() {
override fun run() {
try {
// read from agconnect-services.json
val appId = "Your app id"
val token = HmsInstanceId.getInstance([email protected]).getToken(appId, "HCM")
Log.i(TAG, "get token:$token")
if (!TextUtils.isEmpty(token)) {
sendRegTokenToServer(token)
}
showLog("get token:$token")
} catch (e: ApiException) {
Log.e(TAG, "get token failed, $e")
showLog("get token failed, $e")
}
}
}.start()
}
fun showLog(log: String?) {
runOnUiThread {
val tvView = findViewById<View?>(R.id.tv_log)
val svView = findViewById<View?>(R.id.sv_log)
if (tvView is TextView) {
tvView.text = log
}
if (svView is ScrollView) {
svView.fullScroll(View.FOCUS_DOWN)
}
}
}
private fun sendRegTokenToServer(token: String?) {
Log.i(TAG, "sending token to server. token:$token")
}
companion object {
private const val TAG: String = "PushDemoLog"
private const val CODELABS_ACTION: String = "com.huawei.codelabpush.action"
}
}
In the activity_web_view.xml we can create the UI screen.
XML:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".WebViewActivity">
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
In the log_layout.xml we can create the UI screen.
Code:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView android:id="@+id/sv_log"
android:overScrollMode="never"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:id="@+id/tv_log"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</ScrollView>
Demo
Tips and Tricks
1. Make sure you are already registered as Huawei developer.
2. Set minSDK version to 24 or later, otherwise you will get AndriodManifest merge issue.
3. Make sure you have added the agconnect-services.json file to app folder.
4. Make sure you have added SHA-256 fingerprint without fail.
5. Make sure all the dependencies are added properly.
Conclusion
In this article, we have learned how to integrate the Huawei Push Kit in Book Reading app to send the push message notification to users’ phone from the AppGallery Connect. Push notifications offers a great way to increase your application’s user engagement and boost your retention rates by sending meaningful messages or by informing users about your application. These messages can be sent at any time and even if your app is not running at that time.
I hope you have read this article. If you found it is helpful, please provide likes and comments.
Reference
Push Kit – Document
Push Kit – Training Video

Beginner: Integration of Huawei Push Notification with android Work Manager in Paralysis Glove IoT application and Testing with Postman - Part 2

{
"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"
}
Introduction​
If you are new to series of this article, follow the below article.
Beginner: Integration of Huawei Account kit in Paralysis Glove IoT application - Part 1
In this article, we will learn about Paralysis Glove application and also we will learn integration of the Huawei Push kit in Paralysis Glove IoT application. Here it will cover how exactly push notification works, and benefits of push notification and also we can integrate Huawei Push kit in the smart gloves application. And also we will learn how to test the Push notification from the Postman.
Content​
What is Push Notification?
Advantages of Push notification
Type of notification
Huawei Push notification
Message types.
Integration of push kit
Testing push kit from Postman
In the current mobile world 90% of the applications integrated the push notification. Every application integrate the push notification to engage the users. And also to make better marketing. Every ecommerce application shared the details about offers, discounts, price drops for particular product, the delivery status through the push notification.
First let us understand what push notification is.
Push notification are basically alerts to users from mobile application. Users receives in real-time. Developers can send notification at any time and any day.
Advantages of Push Notification.
User retention: In the world of mobile apps, user retention is a key indicator of how well your app is performing in the market. This metric lets you to see how many users who have downloaded your app and used it once come back to use the app again.
Increase engagement: The power of Huawei push notification is to make user engage by sending some cool notification to users. Notification provides the interaction with application, by this user can spend more time with the application.
Push Notification types
Reminder Notification: Its reminder notification for example recharge reminder, meeting time reminder, appointment reminder etc.
Alert Notification: this type of notification alerts to user when something happens in application which depends upon user. Example when someone sends message, comment on pic.
Promotional notification: these are the promotional notification when application offers something to user example discount, sales date, some weekend sales etc.
Purchas notification: These are valuable notifications and have to do with purchases users make within your app. It can contain information like order confirmation, order status, order updates, tracking, and receipts.
Survey notification: these are feedback or survey notification when application wants to get the feedback or survey at that moment these kind of notification will be used.
Huawei Push Kit is a messaging service provided for you to establish a cloud-to-device messaging channel. By integrating Push Kit, you can send messages to your apps on user devices in real time. This helps you to maintain closer ties with users and increases user awareness of and engagement with your apps.
Push Kit consists of two parts:
Message push from the cloud to the device: enables you to send data and messages to your apps on user devices.
Message display on devices: provides various display styles, such as the notification panel, home screen banner, and lock screen on user devices.
Huawei has 2 types of Messages.
1. Notification Message
2. Data Message
Notification Message: A notification message is directly sent by Push Kit and displayed in the notification panel on the user device, not requiring your app to be running in the background. The user can tap the notification message to trigger the corresponding action such as opening your app or opening a web page. You can tailor the display styles and reminder modes to fit user needs, which will greatly improve the daily active users (DAU) of your app. The common application scenarios of the notification message include subscription, travel reminder, and account status.
Batch message: a message sent by an app in batches to users who will obtain the same content, which can improve user experience and stimulate user interaction with the app.
Personalized message: a message generated based on a unified message template and sent by an app to an audience. The unified message template contains placeholders, which will be replaced based on the settings and preferences of specific users.
Point-to-point message: a message sent by an app to a user when the user takes a specific action.
Instant message: An instant message is a point-to-point or group chatting message (or private message) between users.
Data Message: Data messages are processed by your app on user devices. After a device receives a message containing data or instructions from the cloud, the device passes the message to the app instead of directly displaying it. The app then parses the message and triggers the required action (for example, going to a web page or an app page). For such a message, you can also customize display styles for higher efficiency.
Push Kit cannot guarantee a high data message delivery rate, because it may be affected by Android system restrictions and whether the app is running in the background. The common application scenarios of the data message include the VoIP call, voice broadcast, and interaction with friends.
Prerequisite​
AppGallery Account
Android Studio 3.X
SDK Platform 19 or later
Gradle 4.6 or later
HMS Core (APK) 4.0.0.300 or later
Huawei Phone EMUI 5.0 or later
Non-Huawei Phone Android 5.1 or later
Service integration on AppGallery​
1. We need to register as a developer account in AppGallery Connect.
2. Create an app by referring to Creating a Project and Creating an App in the Project.
3. Set the data storage location based on the current location.
4. Enabling Push Kit Service on AppGallery.
5. Generating a Signing Certificate Fingerprint.
6. Configuring the Signing Certificate Fingerprint.
7. Get your agconnect-services.json file to the app root directory.
Client development
1. Create android project in android studio IDE.
2. Add the maven URL inside the repositories of buildscript and allprojects respectively (project level build.gradle file).
Code:
maven { url 'https://developer.huawei.com/repo/' }
3. Add the classpath inside the dependency section of the project level build.gradle file.
Code:
classpath 'com.huawei.agconnect:agcp:1.5.2.300'
4. Add the plugin in the app-level build.gradle file.
Code:
apply plugin: 'com.huawei.agconnect'
5. Add the below library in the app-level build.gradle file dependencies section.
Code:
implementation 'com.huawei.hms:push:6.3.0.302'
6. Add all the below permission in the AndroidManifest.xml.
XML:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<manifest ...>
...
<application ...>
<service android:name=".push.PushNotificationHmsMessageService" android:exported="false">
<intent-filter>
<action android:name="com.huawei.push.action.MESSAGING_EVENT"/>
</intent-filter>
</service>
</application>
...
</manifest>
7. Sync the project.
Getting Push token in android.
Java:
private fun getPushToken() {
object : Thread() {
override fun run() {
try {
// Obtain the app ID from the agconnect-services.json file.
val appId = "your APP_ID"
// Set tokenScope to HCM.
val tokenScope = "HCM"
val token = HmsInstanceId.getInstance([email protected]).getToken(appId, tokenScope)
Log.i(TAG, "get token:$token")
// Check whether the token is null.
if (!TextUtils.isEmpty(token)) {
sendRegTokenToServer(token)
}
} catch (e: ApiException) {
Log.e(TAG, "get token failed, $e")
}
}
}.start()
}
private fun sendRegTokenToServer(token: String?) {
Log.i(TAG, "sending token to server. token:$token")
}
Whenever new token is generated onNewToken() will be called and you need to send the latest token to backend or server.
Java:
// When new token is generated below method gives the new token
override fun onNewToken(token: String?, bundle: Bundle?) {
// Obtain a push token.
Log.i(TAG, "have received refresh token:$token")
// Check whether the token is null.
if (!token.isNullOrEmpty()) {
refreshedTokenToServer(token)
}
}
private fun refreshedTokenToServer(token: String) {
Log.i(TAG, "sending token to server. token:$token")
}
When push notification is triggered from AGC console or Postman below method will be called. You need to generate notification when onMessageReceived() called.
Java:
//When push notification is triggered from AGC console or Postman below method will be called
override fun onMessageReceived(message: RemoteMessage?) {
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, """getData: ${message.data}
getFrom: ${message.from}
getTo: ${message.to}
getMessageId: ${message.messageId}
getSentTime: ${message.sentTime}
getDataMap: ${message.dataOfMap}
getMessageType: ${message.messageType}
getTtl: ${message.ttl}
getToken: ${message.token}""".trimIndent())
val judgeWhetherIn10s = false
// If the message is not processed within 10 seconds, create a job to process it.
if (judgeWhetherIn10s) {
startWorkManagerJob(message)
} else {
// Process the message within 10 seconds.
processWithin10s(message)
}
}
private fun startWorkManagerJob(message: RemoteMessage?) {
Log.d(TAG, "Start new Job processing.")
}
private fun processWithin10s(message: RemoteMessage?) {
Log.d(TAG, "Processing now.")
}
Now let’s learn the coding part.
Step 1: Create Notification.
NotificationUtils.kt
Java:
package com.huawei.paralysisglove.push
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.media.RingtoneManager
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import kotlin.random.Random
import android.provider.Settings
import com.huawei.paralysisglove.R
import com.huawei.paralysisglove.ui.activities.SplashActivity
class NotificationUtil(private val context: Context) {
fun showNotification(title: String, message: String) {
val intent = Intent(context, SplashActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
val pendingIntent = PendingIntent.getActivity(
context, 0, intent,
PendingIntent.FLAG_ONE_SHOT
)
val channelId = context.getString(R.string.default_notification_channel_id)
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val notificationBuilder = NotificationCompat.Builder(context, channelId)
.setColor(ContextCompat.getColor(context, android.R.color.holo_red_dark))
.setSmallIcon(R.drawable.glove)
.setContentTitle(title)
.setContentText(message)
.setAutoCancel(true)
.setStyle(
NotificationCompat.BigTextStyle()
.bigText(message)
)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent)
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// Since android Oreo notification channel is needed.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
channelId,
"Default Channel",
NotificationManager.IMPORTANCE_HIGH
)
notificationManager.createNotificationChannel(channel)
}
notificationManager.notify(Random.nextInt(), notificationBuilder.build())
}
fun isTimeAutomatic(context: Context): Boolean {
return Settings.Global.getInt(
context.contentResolver,
Settings.Global.AUTO_TIME,
0
) == 1;
}
}
Step 2: Create Worker manager to process data and notification in background. If received data need to be processed and if it takes more than 10 seconds, then create work manager else directly show the notification.
SchedulerWorker.kt
Java:
package com.huawei.paralysisglove.push
import android.content.Context
import android.util.Log
import androidx.work.Worker
import androidx.work.WorkerParameters
class ScheduledWorker(appContext: Context, workerParams: WorkerParameters) :
Worker(appContext, workerParams) {
override fun doWork(): Result {
Log.d(TAG, "Work START")
// Get Notification Data
val title = inputData.getString(NOTIFICATION_TITLE)
val message = inputData.getString(NOTIFICATION_MESSAGE)
// Show Notification
NotificationUtil(applicationContext).showNotification(title!!, message!!)
// TODO Do your other Background Processing
Log.d(TAG, "Work DONE")
// Return result
return Result.success()
}
companion object {
private const val TAG = "ScheduledWorker"
const val NOTIFICATION_TITLE = "notification_title"
const val NOTIFICATION_MESSAGE = "notification_message"
}
}
Step 3: Create broadcast receiver.
NotificationBroadcastReceiver.kt
Java:
package com.huawei.paralysisglove.push
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.work.Data
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import com.huawei.paralysisglove.push.ScheduledWorker.Companion.NOTIFICATION_MESSAGE
import com.huawei.paralysisglove.push.ScheduledWorker.Companion.NOTIFICATION_TITLE
class NotificationBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
intent?.let {
val title = it.getStringExtra(NOTIFICATION_TITLE)
val message = it.getStringExtra(NOTIFICATION_MESSAGE)
// Create Notification Data
val notificationData = Data.Builder()
.putString(NOTIFICATION_TITLE, title)
.putString(NOTIFICATION_MESSAGE, message)
.build()
// Init Worker
val work = OneTimeWorkRequest.Builder(ScheduledWorker::class.java)
.setInputData(notificationData)
.build()
// Start Worker
WorkManager.getInstance().beginWith(work).enqueue()
Log.d(javaClass.name, "WorkManager is Enqueued.")
}
}
}
Step 4: Add NotificationBroadcastReceiver in the AndroidManifest.xml
XML:
<receiver android:name=".push.NotificationBroadcastReceiver" />
Step 5: Create Huawei PushNotificationHmsMessageService.kt
Java:
package com.huawei.paralysisglove.push
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import com.huawei.hms.push.HmsMessageService
import com.huawei.hms.push.RemoteMessage
import android.text.TextUtils
import androidx.work.Data
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import com.google.gson.Gson
import com.huawei.hms.common.ApiException
import com.huawei.hms.aaid.HmsInstanceId
import com.huawei.agconnect.config.AGConnectServicesConfig
import com.huawei.paralysisglove.push.ScheduledWorker.Companion.NOTIFICATION_MESSAGE
import com.huawei.paralysisglove.push.ScheduledWorker.Companion.NOTIFICATION_TITLE
import com.huawei.paralysisglove.push.model.PushModel
import org.json.JSONException
import java.text.SimpleDateFormat
import java.util.*
class PushNotificationHmsMessageService : HmsMessageService() {
override fun onMessageReceived(message: RemoteMessage?) {
Log.i(TAG, "onMessageReceived is called")
if (message == null) {
Log.e(TAG, "Received message entity is null!")
return
}
Log.i(
TAG, """get Data: ${message.data} getFrom: ${message.from}
getTo: ${message.to}
getMessageId: ${message.messageId}
getSendTime: ${message.sentTime}
getDataMap: ${message.dataOfMap}
getMessageType: ${message.messageType}
getTtl: ${message.ttl}
getToken: ${message.token}"""
)
message.data.isNotEmpty().let { it ->
if (it) {
Log.d(TAG, "Message data payload: ${message.data}")
try {
val pushModel: PushModel = Gson().fromJson(message.data, PushModel::class.java)
//val jsonData = JSONObject(message.data)
// Get Message details
val title = pushModel.title
val content = pushModel.message
// Check whether notification is scheduled or not
val isScheduled = pushModel.isScheduled
isScheduled.let {
if (it) {
// Check that 'Automatic Date and Time' settings are turned ON.
// If it's not turned on, Return
val notificationUtil = NotificationUtil(this)
if (!notificationUtil.isTimeAutomatic(applicationContext)) {
Log.d(TAG, "`Automatic Date and Time` is not enabled")
return
}
// This is Scheduled Notification, Schedule it
val scheduledTime = pushModel.scheduledTime
scheduleAlarm(scheduledTime, title, content)
} else {
// This is not scheduled notification, show it now
// Create Notification Data
var body =
"You have reached from " + pushModel.data.fromLocation + " to " + pushModel.data.toLocation
val notificationData = Data.Builder()
.putString(NOTIFICATION_TITLE, title)
.putString(NOTIFICATION_MESSAGE, body)
.build()
// Init Worker
val work = OneTimeWorkRequest.Builder(ScheduledWorker::class.java)
.setInputData(notificationData)
.build()
// Start Worker
WorkManager.getInstance(this).beginWith(work).enqueue()
Log.d(javaClass.name, "WorkManager is Enqueued.")
}
}
} catch (e: JSONException) {
e.printStackTrace()
}
} else {
val notificationData = Data.Builder()
.putString(NOTIFICATION_TITLE, message.notification.title)
.putString(NOTIFICATION_MESSAGE, message.notification.body)
.build()
// Init Worker
val work = OneTimeWorkRequest.Builder(ScheduledWorker::class.java)
.setInputData(notificationData)
.build()
// Start Worker
WorkManager.getInstance(this).beginWith(work).enqueue()
}
}
}
private fun scheduleAlarm(
scheduledTimeString: String?,
title: String?,
message: String?
) {
val alarmMgr = applicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val alarmIntent =
Intent(applicationContext, NotificationBroadcastReceiver::class.java).let { intent ->
intent.putExtra(NOTIFICATION_TITLE, title)
intent.putExtra(NOTIFICATION_MESSAGE, message)
PendingIntent.getBroadcast(applicationContext, 0, intent, 0)
}
// Parse Schedule time
val scheduledTime = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
.parse(scheduledTimeString!!)
scheduledTime?.let {
// With set(), it'll set non repeating one time alarm.
alarmMgr.set(
AlarmManager.RTC_WAKEUP,
it.time,
alarmIntent
)
}
}
override fun onNewToken(token: String) {
Log.i(TAG, "received refresh token:$token")
if (!TextUtils.isEmpty(token)) {
refreshedTokenToServer(token)
}
}
// If the version of the Push SDK you integrated is 5.0.4.302 or later, you also need to override the method.
override fun onNewToken(token: String, bundle: Bundle?) {
Log.i(TAG, "have received refresh token $token")
if (!TextUtils.isEmpty(token)) {
refreshedTokenToServer(token)
}
}
private fun refreshedTokenToServer(token: String) {
Log.i(TAG, "sending token to server. token:$token")
}
private fun deleteToken() {
// Create a thread.
object : Thread() {
override fun run() {
try {
// Obtain the app ID from the agconnect-service.json file.
val appId =
AGConnectServicesConfig.fromContext([email protected])
.getString("client/app_id")
// Set tokenScope to HCM.
val tokenScope = "HCM"
// Delete the token.
HmsInstanceId.getInstance([email protected])
.deleteToken(appId, tokenScope)
Log.i(TAG, "token deleted successfully")
} catch (e: ApiException) {
Log.e(TAG, "deleteToken failed.$e")
}
}
}.start()
}
/**
* Persist token to third-party servers.
*
* Modify this method to associate the user's FCM registration token with any server-side account
* maintained by your application.
*
* @param token The new token.
*/
private fun sendRegistrationToServer(token: String?) {
// TODO: Implement this method to send token to your app server.
Log.d(TAG, "sendRegistrationTokenToServer($token)")
}
companion object {
private const val TAG = "PushNotificationHmsMessageService"
}
}
Testing on Postman
Notification Message
Step 1: Download below json file.
JSON:
{
"info": {
"_postman_id": "6ee69330-4edb-4b0c-b235-4247daa3e2ea",
"name": "PushKitTest",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Send notification",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "Bearer CgB6e3x9aAsJsxGgxgasZWXyu63iI9H/bwuVZFKbJfRjcCjP0LCJYpep4UAkZj33jknaHBRLWTRnTxHKcMfzOxBj",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"name": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"validate_only\": false,\n \"message\": \n {\n \"notification\":\n {\n \"title\": \"Huawei Push Kit\",\n \"body\": \"Hello, I'm a Push Kit.\"\n },\n \"android\":\n {\n \"notification\":\n {\n \"click_action\":\n {\n \"type\": 3\n }\n }\n },\n \"token\": [\"ANPpqLe8aTPl9tsB2Iu_820GmUQ-prH-dzPV-orbkm9yym2bitxd3emgxglrkVJW8hbTkusjHCu2_RW-AdnqqSbHevBQYwhjqOBzCdcu_Zrqfzuowi7Sxp4c2r_ztTORVQ\"]\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "https://push-api.cloud.huawei.com/v1/103346107/messages:send",
"protocol": "https",
"host": [
"push-api",
"cloud",
"huawei",
"com"
],
"path": [
"v1",
"103346107",
"messages:send"
]
}
},
"response": []
},
{
"name": "Send data message",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "Bearer CgB6e3x9aAsJsxGgxgasZWXyu63iI9H/bwuVZFKbJfRjcCjP0LCJYpep4UAkZj33jknaHBRLWTRnTxHKcMfzOxBj",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"name": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"validate_only\": false,\n \"message\": \n {\n \"data\": \"{\\\"title\\\":\\\"Huawei Push Kit\\\",\\\"text\\\":\\\"Hi, I'm a Push Kit.\\\",\\\"channel_id\\\":\\\"channel_1\\\"}\",\n \"token\": [\"ANPpqLe8aTPl9tsB2Iu_820GmUQ-prH-dzPV-orbkm9yym2bitxd3emgxglrkVJW8hbTkusjHCu2_RW-AdnqqSbHevBQYwhjqOBzCdcu_Zrqfzuowi7Sxp4c2r_ztTORVQ\"]\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "https://push-api.cloud.huawei.com/v1/103346107/messages:send",
"protocol": "https",
"host": [
"push-api",
"cloud",
"huawei",
"com"
],
"path": [
"v1",
"103346107",
"messages:send"
]
}
},
"response": []
},
{
"name": "Get access token",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"name": "Content-Type",
"type": "text",
"value": "application/x-www-form-urlencoded"
}
],
"body": {
"mode": "urlencoded",
"urlencoded": [
{
"key": "grant_type",
"value": "client_credentials",
"type": "text"
},
{
"key": "client_id",
"value": "103346107",
"type": "text"
},
{
"key": "client_secret",
"value": "1e35055832e028a9b07251ad1f9192b2ffbf4551cd71bfbb005875b046f0b981",
"type": "text"
}
]
},
"url": {
"raw": "https://oauth-login.cloud.huawei.com/oauth2/v2/token",
"protocol": "https",
"host": [
"oauth-login",
"cloud",
"huawei",
"com"
],
"path": [
"oauth2",
"v2",
"token"
]
}
},
"response": []
}
]
}
Step 2: Open Postman. Import the file. (Postman > file > import) After you import, we can send 3 requests.
Step 3: Replace the app_id and app_secret values in the body with the App ID and App secret values on the application home page of the Huawei Console. Replace the fields in the image below with the values in your own project.
Step 4: Get push token in MainActivity.kt
Java:
private fun getToken() {
object : Thread() {
override fun run() {
try {
val appId = AGConnectOptionsBuilder().build([email protected])
.getString("client/app_id")
val token = HmsInstanceId.getInstance([email protected])
.getToken(appId, "HCM")
Log.i("PUSH", "getToken() token: $token")
} catch (e: ApiException) {
Log.e("PUSH", "getToken() failure: ${e.message}")
}
}
}.start()
}
Step 5: Open the Get access token file and click the Send button to get access token. The access token is valid for 3600 seconds and you need to renew it late.
Step 6: Open the Send notification and Send data message file and replace the access_token part in Authorization with the access token we got in before step. Don't forget to delete this characters \ in the access token.
After authorization token replaces, let us send the notification now.
Send Notification
Body:
JSON:
{
"validate_only": false,
"message":
{
"notification":
{
"title": "Push Ki Demo ",
"body": "Enter body here."
},
"android":
{
"notification":
{
"click_action":
{
"type": 3
}
}
},
"token": ["Add your push token here"]
}
}
Data Message
Body:
JSON:
{
"validate_only": false,
"message":
{
"data": "{\"title\":\"Huawei Push Kit\",\"text\":\"Hi, I'm a Push Kit.\",\"channel_id\":\"channel_1\"}",
"token": ["Add your push token here",
"Add your push token here"]
}
}
Result​
​Tips and Tricks​
1. Make sure you are already registered as a Huawei developer.
2. Set min SDK version to 19 or later, otherwise you will get AndriodManifest to merge issue.
3. Make sure you have added the agconnect-services.json file to the app folder.
4. Make sure you have added the SHA-256 fingerprint without fail.
5. Make sure all the dependencies are added properly.
Conclusion​
In this article, we have learnt the integration of the Huawei Push Kit in Paralysis Gloves mobile application using Android Studio and Kotlin. And also understood about Huawei Push kit, types of messages, notification type, How to test push notification from AppGallery console.
Reference​
Push Kit - Official document
Push Kit - Code lab
Push Kit - Training Video

Categories

Resources