Introduction to the App Gallery connect API [kotlin] - Huawei Developers

On an Enterprise environment maybe you want to perform some console operations from a custom plaform, for example, to manage your app information, to automatize the download of the app finance report, or to automatically release an app allocated in your own server. If this is your case, yo may be interested on App Gallery Connect API.
Previous requirements
A developer account
At least one project on AGC
What will we do in this article?
We will generate our client credentials to obtain an app level access token which will give us access to the App Gallery Connect API. After that we wiil use the Connect API to perform the next operations.
Obtaining the App Id related to an app package name
Obtaining the basic app information
Obtaining the upload URL to upload a new version of our app
Obtaining the URL to download your App Report
All the API requests on this article will be performed by using Kotlin, so you can use it to develop your own management platform for mobile, desktop or web.
Generating the client credentials
Sign into AppGallery Connect and then go to Users and permissionfrr4qwq
{
"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"
}
From the left side panel, select Connect API and click on the Create button to generate your client credentials.
In the pop up dialog, choose a name, project and applicable roles for your API client. In this example, I will use Administrator.
Note: Some APIs require project-level authorization. Please specify the projects you want to access through these APIs. Select N/A if the APIs only require team-level authorization.
Once you have confirmed your settings, your client ID and secret key will appear on the Connect API screen.
Copy your Client ID and Key. Make sure to keep them safe.
Request Helper
I wrote a helper class to perform the REST calls to the Connect API
Code:
data class ResponseObject(val code:Int,val message: String,var responseBody:JSONObject?)
class RequestHelper {
companion object{
fun sendRequest(host:String, headers: HashMap<String,String>?, body:JSONObject?, requestType:String="GET"):ResponseObject{
try {
val conn = URL(host)
.openConnection() as HttpURLConnection
conn.apply {
requestMethod = requestType
headers?.apply {
for(key in keys)
setRequestProperty(key, get(key))
}
doOutput = requestType == "POST"
doInput = true
}
if(requestType!="GET"){
conn.outputStream.let{
body?.apply { it.write(toString().toByteArray()) }
}
}
val result = when (conn.responseCode) {
in 0..300 -> convertStreamToString(conn.inputStream)
else -> convertStreamToString(conn.errorStream)
}
//Returns the access token, or an empty String if something fails
return ResponseObject(conn.responseCode,conn.responseMessage, JSONObject(result)).also { conn.disconnect() }
} catch (e: Exception) {
return ResponseObject(400,e.toString(),null)
}
}
private fun convertStreamToString(input: InputStream): String {
return BufferedReader(InputStreamReader(input)).use {
val response = StringBuffer()
var inputLine = it.readLine()
while (inputLine != null) {
response.append(inputLine)
inputLine = it.readLine()
}
it.close()
response.toString()
}
}
}
}
Obtaining the App Level Access Token
This is a mandatory step, the access token contains information about the scope level provided to your client credentials.
Perform the following POST request:
Hostname: connect-api.cloud.huawei.com
Path: /api/oauth2/v1/token
Headers:
Content-Type: application/json
Body:
Code:
{
"grant_type":"client_credentials",
"client_id":"YOUR_CLIENT ID",
"client_secret":"YOUR_CLIENT_KEY"
}
If everything goes fine, the request will return an access token and the validity period of the token. You can use the same access token for any of the following operations until the expiration time. If the token expires, you must apply for a new one.
Example:
Code:
data class AGCredential(val clientId: String, val key: String)
class AGConnectAPI(credential: AGCredential) {
companion object {
@JvmField
val HOST = "https://connect-api.cloud.huawei.com/api"
private val MISSING_CREDENTIALS = "must setup the client credentials first"
val MISSING_CREDENTIALS_RESPONSE=ResponseObject(403, MISSING_CREDENTIALS, null)
}
var token: String? = null
var credential: AGCredential= credential
set(value) {
field = value
getToken()
}
init {
getToken()
}
private fun getToken(): Int {
val host = "$HOST/oauth2/v1/token"
val headers = HashMap<String, String>().apply {
put("Content-Type", "application/json")
}
val body = JSONObject().apply {
put("client_id", credential.clientId)
put("client_secret", credential.key)
put("grant_type", "client_credentials")
}
val response = RequestHelper.sendRequest(host, headers, body, "POST")
val token = response.responseBody?.let {
if (it.has("access_token")) {
it.getString("access_token")
} else null
}
return if (token != null) {
this.token = token
200
} else response.code
}
}
Obtaining the App Id for a given package name
The App Id is required as a unique identifier for app management operations.
Perform the following GET request:
Hostname: connect-api.cloud.huawei.com
Path: /api/publish/v2/appid-list?packageName=$packageName
Headers:
Authorization: Bearer $token
client_id: clientId
Example
Code:
fun queryAppId(packageName: String): ResponseObject {
return if (!token.isNullOrEmpty()) {
val url = "$HOST/publish/v2/appid-list?packageName=$packageName"
RequestHelper.sendRequest(url, getClientHeaders(), null)
} else MISSING_CREDENTIALS_RESPONSE
}
private fun getClientHeaders(): HashMap<String, String> {
return HashMap<String, String>().apply {
put("Authorization", "Bearer $token")
put("client_id", credential.clientId)
}
}
Obtaining the upload URL to upload a new version of our app
The Connect API provides URLs to upload different files of your app configuration in AppGallery. For this example we will get the URL to upload the APK. Currently, files with the following extensions can be uploaded:
apk/rpk/pdf/jpg/jpeg/png/bmp/mp4/mov/aab.
Perform the following GET request:
Hostname: connect-api.cloud.huawei.com
Path: /api//publish/v2/upload-url?appId=$appId&suffix=$(apk/rpk/pdf/jpg/jpeg/png/bmp/mp4/mov/aab)
Headers:
Content-Type: application/json
Authorization: Bearer $token
client_id: clientId
Example:
Code:
fun getUploadURL(appId: String,suffix: String):ResponseObject{
return if (!token.isNullOrEmpty()){
val url="$HOST/publish/v2/upload-url?appId=$appId&suffix=$suffix"
RequestHelper.sendRequest(url,getClientHeaders(),null)
} else MISSING_CREDENTIALS_RESPONSE
}
Obtaining the URL to download your App Report
You can download detailed reports about your app downloads and installations, purchases made in your app and if your app is a paid app, you can get the Paid Download report.
For the Paid Download report the hostname is different for every region
Hostname: https://{domain}
Domain name for China: connect-api.cloud.huawei.com
Domain name for Europe: connect-api-dre.cloud.huawei.com
Domain name for Asia Pacific: connect-api-dra.cloud.huawei.com
Domain name for Russia: connect-api-drru.cloud.huawei.com
Path: /api/report/distribution-operation-quality/v1/orderDetailExport/$appId
Headers:
Authorization: Bearer $token
client_id: clientId
This API require some querys to generate the report, to se the query details, refer to the official documentation
Example:
Code:
fun getReportUrl(
appId: String,
lang: String,
startTime: String,
endTime: String,
filterConditions:HashMap<String,String>
):ResponseObject {
return if (!token.isNullOrEmpty()){
val fc = StringBuilder().apply {
for (key in filterConditions.keys) {
append("&filterCondition=")
append(key)
append("&filterConditionValue=")
append(filterConditions[key])
}
}
val url =
"$HOST/report/distribution-operation-quality/v1/orderDetailExport/$appId?language=$lang&startTime=$startTime&endTime=$endTime${fc}"
RequestHelper.sendRequest(url,getClientHeaders(),null)
} else MISSING_CREDENTIALS_RESPONSE
}
Conclusion
Now you know what is the Connect API and how you can use it to automatize some management operations or to develop yur own custom console. There are also a lot of things what you can do with the Connect API. For more information you can refer to the official documentation.
Check this and other demos: https://github.com/danms07/dummyapp

Using this API can we customize Roles and permissions user basis?

Related

Huawei Wallet Kit Server API ( Node js Server )

More information like this, you can visit HUAWEI Developer Forum
{
"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"
}
Co-author by Sanghati Mukherjee and Sujith.
The article is the continuity of our Huawei kit series. The series contain four parts. Below are the links:
1) It’s Show Time using Huawei Auth Service and Account kit.
2) It’s Show Time using Huawei Location and Site kit.
3) It’s Show Time using Huawei Wallet kit.
The fourth part is this article where we would learn how to implement the server side of Huawei wallet kit using local Node js server and how to call them in client side in order to create ticket and save the ticket on user wallet app for future use.
Why having your own server?
Using our own server to communicate with app is generally the best option for creating cards or tickets in our application. Because, our app will recognize and trust our own server, allowing us to control all transactions between our server and user devices. That is what we are going to learn today.
Setting up the server
If you are not familiar with setting up your own local server and connecting the server with the device, I would strongly recommend you to go through my previous article i.e. “Build your own server from scratch to send push notification“. My previous article will help you setting up local server from scratch also it is healthy to learn something new every day as our brain can store more information than a computer.
Prerequisite
1) We must have latest version of Node installed.
2) We must have latest version of Visual Studio Code installed.
3) We must have latest version of MongoDB database installed.
4) Laptop/desktop and Huawei mobile device must share same Wi-Fi connection.
5) We must have a working app integrate with HMS Wallet Kit.
We will divide this article into two parts
1) Server Side: The server side contains Node, Express, Request and JavaScript.
2) Client Side: The client side contains Android Native, Java, Retrofit and HMS Wallet Kit.
Demo
Server Side
Obtaining app-level access token API
The request header uses AccessToken for authentication, which is obtained using the service API provided by the open platform. The service API provides two modes to obtain AccessToken: authorization code mode and client password mode. This API uses the client password mode.
Do not apply for a new app-level access token each time before the server API is called. Frequent application requests may trigger rejection, leading to application failure within a specified period. Each access token has a validity period.
Within the validity period, it can be used repeatedly. You are advised to apply for an access token again only when the server API is accessed and HTTP result code 401 is returned.
Code:
var request = require("request");
const getAppToken = (callBack) => {
var options = {
method: 'POST',
url: 'https://oauth-login.cloud.huawei.com/oauth2/v3/token',
headers:
{
'content-type': 'application/x-www-form-urlencoded',
host: 'Login.cloud.huawei.com',
post: '/oauth2/v2/token HTTP/1.1'
},
form:
{
grant_type: "client_credentials",
client_secret: 'Put Your Client Secret Here...',
client_id: 'Put Your APP ID Here ...'
}
};
request(options, function (error, response, body) {
if (error) throw new Error(error);
var tokenValue = JSON.parse(body);
callBack(tokenValue.access_token);
});
}
exports.getAppToken = getAppToken;
Wallet server address
Set the {url} variable based on the region where the server is located. For details, please refer to Wallet Server Address as shown below.
Creating a HwWalletObject
We need to set HwWalletObject as body parameter in order to call wallet event ticket APIs. To know more about HwWalletObject follow the link below:
https://developer.huawei.com/consumer/en/doc/HMSCore-References-V5/def-0000001050160319-V5
Creating an event ticket model API
We call this method to add an event ticket model to the Huawei server.
URL: https://passentrust-dra.wallet.hicloud.com/hmspass/v1/eventticket/model
API
Code:
var request = require("request");
const getEventTIcketModelObject = (authorizationVal, ticketBookingDate,callBack) => {
var options = {
method: 'POST',
url: 'https://passentrust-dra.wallet.hicloud.com/hmspass/v1/eventticket/model',
headers:
{
'cache-control': 'no-cache',
accept: 'application/json',
authorization: 'Bearer '+authorizationVal,
'content-type': 'application/json'
},
body:
{ passVersion: '1.0',
passTypeIdentifier: 'YOUR_SERVICE_ID',
passStyleIdentifier: 'YOUR_MODEL_ID',
organizationName: 'Huawei',
fields:
{ countryCode: 'zh',
locationList: [ { longitude: '114.0679603815', latitude: '22.6592051284' } ],
commonFields:
[
{ key: 'logo', value: 'https://contentcenter-drcn.dbankcdn.com/cch5/Wallet-WalletKit/picres/cloudRes/coupon_logo.png' },
{ key: 'name', value: 'Is Show Time Movie Ticket' },
{ key: 'merchantName',
value: 'Huawei',
localizedValue: 'merchantNameI18N' },
{ key: 'address', value: 'INOX Cinema' },
{ key: 'ticketType', value: 'Movie ticket' } ],
appendFields: [ { key: 'backgroundColor', value: '#3e454f' } ],
timeList:
[ { key: 'startTime', value: ticketBookingDate },
{ key: 'endTime', value: '2020-08-20T00:00:00.111Z' } ],
localized:
[ { key: 'merchantNameI18N', language: 'zh-cn', value: '华为' },
{ key: 'merchantNameI18N', language: 'en', value: 'Huawei' }
]
}
},
json: true };
request(options, function (error, response, body) {
if (error) {
callBack(error,error);
}
else{
callBack(body);
}
});
}
exports.getEventTIcketModelObject = getEventTIcketModelObject;
The value of passTypeIdentifier parameter is the Service ID, which can be obtained when we apply for HUAWEI Wallet Kit service in AGC and the value of passStyleIdentifier is the Model ID, which can also be obtained from wallet kit service in AGC. Both are mandatory.
Adding an event ticket instance API
We call this method to add the event ticket instance of a user to the Huawei server. After the instance is added using this API, a thin JWE should be used as well to link the instance to the user's HUAWEI ID. Alternatively, a JWE can be used, rather this API, to directly add the instance to the Huawei server and link it to the user's HUAWEI ID.
URL: https://passentrust-dra.wallet.hicloud.com/hmspass/v1/eventticket/instance
API
Code:
var request = require("request");
const getEventTIcketInstanceObject = (authorizationVal, serialNumber, ticketBookingDate,seatNumber,userName, movieName,callback) => {
var options = {
method: 'POST',
url: 'https://passentrust-dra.wallet.hicloud.com/hmspass/v1/eventticket/instance',
headers:
{
'cache-control': 'no-cache',
accept: 'application/json',
authorization: 'Bearer '+authorizationVal,
'content-type': 'application/json' },
body:
{
organizationPassId: 'YOUR_APP_ID',
passTypeIdentifier: 'YOUR_SERVICE_ID',
passStyleIdentifier: 'YOUR_MODEL_ID',
serialNumber: serialNumber,
fields:
{ status:
{ state: 'active',
effectTime: ticketBookingDate,
expireTime: '2020-08-20T00:00:00.111Z' },
barCode:
{ text: '562348969211212',
type: 'codabar',
value: '562348969211212',
encoding: 'UTF-8' },
commonFields:
[ { key: 'ticketNumber', value: serialNumber },
{ key: 'title', value: 'Its Show Time' },
{ key: 'name', value: movieName },
{key:'programImage', value:'https://contentcenter-drcn.dbankcdn.com/cch5/Wallet-WalletKit/picres/cloudRes/coupon_logo.png'}
],
appendFields:
[ { key: 'gate', value: 'Gate 2', label: 'Gate' },
{ key: 'seat', value: '24', label: 'Seat' },
{ key: 'userName', value: userName }
]
}
},
json: true };
request(options, function (error, response, body) {
if (error) throw new Error(error);
//console.log(body);
var data = JSON.parse(JSON.stringify(body));
// console.log(data)
callback(body);
});
}
exports.getEventTIcketInstanceObject = getEventTIcketInstanceObject;
The value of organizationPassId parameter is the APP ID, which can be obtained from our AGC.
Create ticket instance API for client
We need an API for client to call the above two APIs on server in order to fetch wallet instance and use it to create a movie ticket.
Code:
app.post('/createTicket', (req, res, next) => {
appTokenWallet.getAppToken((callBack) => {
let authorization = callBack
console.log(authorization);
let serialNumber = Math.floor(Math.random() * 50000) + 100000;
console.log(serialNumber);
let ticketBookingDate = convertDateToUTC();
createTicketModel.getEventTIcketModelObject(authorization,ticketBookingDate, callBackMessage => {
console.log(callBackMessage);
});
createTicketInstance.getEventTIcketInstanceObject(authorization,serialNumber,ticketBookingDate,req.body.seat,req.body.username,"Dil Bechara",callBackMsg => {
if(callBackMsg!=null){
res.send(""+serialNumber);
}
});
});
});
Client Side (Android Native)
We need Retrofit in order to call our restful Apis. Include the following dependencies in app build.gradle file.
Code:
implementation 'com.squareup.retrofit2:retrofit:2.8.1'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
Create retrofit interface
Create a new Interface class and name it GetDataService. Open the class, copy and paste below code:
Code:
public interface GetDataService {
@POST("/createTicket")
Call<Object> createMovieTicket(@Body HashMap<String, String> map);
}
Create retrofit instance
Create a new class and name it RetrofitClientInstance. Open the class, copy and paste below code:
Code:
public class RetrofitClientInstance {
private static Retrofit retrofit;
public static Retrofit getRetrofitInstance() {
if (retrofit == null) {
retrofit = new retrofit2.Retrofit.Builder()
.baseUrl(Constant.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
Note: The BASE_URL is important here. We will put our IPv4 Address of our server machine instead localhost. To find our machine IPv4 Address, we will go to command prompt and type ipconfig. Also make sure that the device is connected to the same Wi-Fi the server machine is connected too.
Use retrofit instance in the activity
Create an object of retrofit instance in onCreate() method of the activity as show below:
Code:
service = RetrofitClientInstance.getRetrofitInstance().create(GetDataService.class);
After that use this retrofit instance object to call the create ticket instance API of server in order to fetch instance Id and use it to create thin JWE which will be used to create a ticket.
Code:
public void proceedToPay(View view) {
progressBar.setVisibility(View.VISIBLE);
HashMap<String, String> ticket = new HashMap<>();
ticket.put("seat", seats);
ticket.put("username","Sanghati Mukherjee");
Call<Object> call = service.createMovieTicket(ticket);
call.enqueue(new Callback<Object>() {
@Override
public void onResponse(Call<Object> call, Response<Object> response) {
passObject = "{\"instanceIds\": [\""+response.body().toString().replace(".0","").trim()+"\"]}";
progressBar.setVisibility(View.GONE);
generateJWEStr();
}
@Override
public void onFailure(Call<Object> call, Throwable t) {
Toast.makeText(PaymentActivity.this, t.getMessage(), Toast.LENGTH_LONG).show();
System.out.println("RESPONSE >>> " + t.getMessage());
progressBar.setVisibility(View.GONE);
}
});
}
When JWE is created the Huawei server will call browser to create ticket as shown below:
Now we need to add the ticket in our wallet app for future use which in this case is a movie ticket for INOX cinemas.
GitHub Links
Client Side: https://github.com/DTSE-India-Community/ItsShowTime
Server Side: https://github.com/DTSE-India-Community/Huawei-In-App-Purchase-Push-Kit-Server_Side-And-Wallet-Kit-Server-Side-Implementation
For More Information
https://developer.huawei.com/consumer/en/doc/development/HMSCore-References-V5/create-model-0000001050158460-V5
https://developer.huawei.com/consumer/en/doc/development/HMSCore-References-V5/add-instance-0000001050158466-V5

Samachar a news application using HMS Push Kit Client + Server Side (Part2)

More information like this, you can visit HUAWEI Developer Forum
{
"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"
}
This article is the continuity of my previous article. To get a better picture or knowledge, refer my previous article first. Below is the link:
https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0202326236067510002&fid=0101187876626530001
Why you are having own server?
Using our own server to communicate with our app is generally the best option for sending push notification directly to our application. Because, our app will recognize and trust our own server which will allow us to control all transactions between our server and user devices.
Also if we need to send notification using Huawei AGC console, we need to copy and paste our users push token every time, which is a tedious job. Having our own server will allow us to store our users token and can send notification easily by fetching the entire list of token from database. That is what we are going to learn today.
Setting up the server
If you are not familiar with setting up your own local server and connecting the server with the device, refer my previous article “Build your own server from scratch to send push notification“. My previous article will help you setting up local server from scratch also, it is healthy to learn something new every day as our brain can store more information than a computer.
Prerequisite
1) We must have latest version of Node installed.
2) We must have latest version of Visual Studio Code installed.
3) We must have latest version of MongoDB database installed.
4) Laptop/desktop and Huawei mobile device must share same Wi-Fi connection.
5) We must have a working app integrate with HMS Push Kit.
We will divide this article into two parts
1) Server Side: The server side contains Node, Express, Request and JavaScript.
2) Client Side: The client side contains Android Native, Java, Retrofit and HMS Push Kit.
Demo
Send notification from server
Notification on device side
Goal
Our goal here is to subscribe, unsubscribe and send notification using topic-based message sending, also when user will tap the notification it will take user to a specific page. All this will be done using our local server side.
Server Side
Obtaining app-level access token API
The request header uses AccessToken for authentication, which is obtained using the service API provided by the open platform. The service API provides two modes to obtain AccessToken: authorization code mode and client password mode. This API uses the client password mode.
Do not apply for a new app-level access token each time before the server API is called. Frequent application requests may trigger rejection, leading to application failure within a specified period. Each access token has a validity period.
Within the validity period, it can be used repeatedly. You are advised to apply for an access token again, only when the server API is accessed and HTTP result code 401 is return
Code:
var request = require("request");
const getAppToken = (callBack) => {
var options = {
method: 'POST',
url: 'https://oauth-login.cloud.huawei.com/oauth2/v3/token',
headers:
{
'content-type': 'application/x-www-form-urlencoded',
host: 'Login.cloud.huawei.com',
post: '/oauth2/v2/token HTTP/1.1'
},
form:
{
grant_type: "client_credentials",
client_secret: 'Put Your Client Secret Here...',
client_id: 'Put Your APP ID Here ...'
}
};
request(options, function (error, response, body) {
if (error) throw new Error(error);
var tokenValue = JSON.parse(body);
callBack(tokenValue.access_token);
});
}
exports.getAppToken = getAppToken;
Subscribe to a topic API
The API is used to subscribe to a topic for one or more users. A maximum of 1,000 tokens are allowed in each call request. Currently, this API only supports Android apps.
URL: https://push-api.cloud.huawei.com/v1/[appid]/topic:subscribe
API
subscribeToTopic.js
Code:
var request = require("request");
const subscribeToTopicNotify = (appLevelToken, tokenVal, topicSubscribeVal, callBack) => {
var options = {
method: 'POST',
url: 'https://push-api.cloud.huawei.com/v1/[appid]/topic:subscribe',
headers:
{
authorization: 'Bearer ' + appLevelToken,
host: 'oauth-login.cloud.huawei.com',
post: '/oauth2/v2/token HTTP/1.1',
'content-type': 'application/json'
},
body:
{
topic: topicSubscribeVal,
tokenArray: tokenVal
},
json: true
};
request(options, function (error, response, body) {
if (error) throw new Error(error);
// console.log(body);
var notify = JSON.parse(JSON.stringify(body));
// console.log(">>>"+notify.msg);
callBack(notify.msg);
});
}
exports.subscribeToTopicNotify = subscribeToTopicNotify;
app.js
Code:
app.post('/subscribeToTopic', (req, res, next) => {
// topicSubscribeVal = req.body.topic;
let topic = {
topic: req.body.topic,
};
console.log(topic.topic);
// Fetching Token from mongodb ...
dbase.collection('token').find().toArray((err, results) => {
if (results) {
//clear the token list ...
arrToken = [];
// adding token in the list ...
for (var i = 0; i < results.length; i++) {
console.log("Token " + i + " " + results[i].token);
arrToken.push(results[i].token);
}
// if error code 401 returned it means access token becomes invalid
// and we need to obtain a new token. Token Validity is 60 mins...
// fetchin app level access token here ...
appToken.getAppToken((callBack) => {
console.log("TOKEN >>>" + callBack);
appLevelAccesToken = callBack;
subscribeToTopic.subscribeToTopicNotify(appLevelAccesToken, arrToken, topic.topic,callBackMessage => {
notifySucesssMessage = callBackMessage;
console.log(notifySucesssMessage);
if(callBackMessage!=null){
res.send(callBackMessage);
}
});
});
}
});
});
Unsubscribe to a topic API
The API is used to unsubscribe to a topic for one or more users. A maximum of 1,000 tokens are allowed in each call request. Currently, this API only supports Android apps.
URL: https://push-api.cloud.huawei.com/v1/[appid]/topic:unsubscribe
API
unSubscribeToTopic.js
Code:
var request = require("request");
const unSubscribeToTopicNotify = (appLevelToken, tokenVal, topicSubscribeVal, callBack) => {
var options = {
method: 'POST',
url: 'https://push-api.cloud.huawei.com/v1/[appid]/topic:unsubscribe',
headers:
{
authorization: 'Bearer ' + appLevelToken,
host: 'oauth-login.cloud.huawei.com',
post: '/oauth2/v2/token HTTP/1.1',
'content-type': 'application/json'
},
body:
{
topic: topicSubscribeVal,
tokenArray: tokenVal
},
json: true
};
request(options, function (error, response, body) {
if (error) throw new Error(error);
// console.log(body);
var notify = JSON.parse(JSON.stringify(body));
// console.log(">>>"+notify.msg);
callBack(notify.msg);
});
}
exports.unSubscribeToTopicNotify = unSubscribeToTopicNotify;
app.js
Code:
// Push Kit unsubscribe to a topic ...
app.post('/unsubscribeToTopic', (req, res, next) => {
let topic = {
topic: req.body.topic,
};
// Fetching Token from mongodb ...
dbase.collection('token').find().toArray((err, results) => {
if (results) {
//clear the token list ...
arrToken = [];
// adding token in the list ...
for (var i = 0; i < results.length; i++) {
console.log("Token " + i + " " + results[i].token);
arrToken.push(results[i].token);
}
// if error code 401 returned it means access token becomes invalid
// and we need to obtain a new token. Token Validity is 60 mins...
// fetchin app level access token here ...
appToken.getAppToken((callBack) => {
console.log("TOKEN >>>" + callBack);
appLevelAccesToken = callBack;
unSubscribeToTopic.unSubscribeToTopicNotify(appLevelAccesToken, arrToken, topic.topic,callBackMessage => {
notifySucesssMessage = callBackMessage;
console.log(notifySucesssMessage);
if(callBackMessage!=null){
res.send(callBackMessage);
}
});
});
}
});
});
Querying the topic subscription list API
The API is used to query the topic subscription list of a token. Currently, this API only supports Android apps.
URL: https://push-api.cloud.huawei.com/v1/[appid]/topic:list
API
getTopicList.js
Code:
var request = require("request");
const getTopicList = (appLevelToken, tokenVal, callBack) => {
var options = {
method: 'POST',
url: 'https://push-api.cloud.huawei.com/v1/[appid]/topic:list',
headers:
{
authorization: 'Bearer ' + appLevelToken,
host: 'oauth-login.cloud.huawei.com',
post: '/oauth2/v2/token HTTP/1.1',
'content-type': 'application/json'
},
body:
{
token: tokenVal
},
json: true
};
request(options, function (error, response, body) {
if (error) throw new Error(error);
// console.log(body);
var notify = JSON.parse(JSON.stringify(body));
console.log(">>>"+notify);
callBack(notify.topics);
});
}
exports.getTopicList = getTopicList;
app.js
Code:
app.post('/getTopicList', (req, res, next) => {
console.log("CALLLLLING")
// Fetching Token from mongodb ...
dbase.collection('token').find().toArray((err, results) => {
if (results) {
//clear the token list ...
arrToken = [];
// adding token in the list ...
for (var i = 0; i < results.length; i++) {
console.log("Token " + i + " " + results[i].token);
arrToken.push(results[i].token);
}
var uniqueItems = Array.from(new Set(arrToken));
// if error code 401 returned it means access token becomes invalid
// and we need to obtain a new token. Token Validity is 60 mins...
// fetchin app level access token here ...
appToken.getAppToken((callBack) => {
console.log("TOKEN VA L >>>" + uniqueItems);
appLevelAccesToken = callBack;
getTopicList.getTopicList(appLevelAccesToken, uniqueItems[0],callBackMessage => {
notifySucesssMessage = callBackMessage;
console.log("MSG >>>>>>>>>"+notifySucesssMessage);
res.send(notifySucesssMessage);
});
});
}
});
});
Sending notification using topic subscription API
The API is used to send notification using topic subscription, also an intent to send user to specific page. In this case we are sending URL as a data in an intent which will make user to view the website in android WebActivity page on taping the notification.
URL: https://push-api.cloud.huawei.com/v1/[appid]/messages:send
This is not the end. For full content, you can visit https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0201327882870070045&fid=0101187876626530001

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?

Getting Latest Corona News with Huawei Search Kit

{
"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 Search Kit includes device-side SDK and cloud-side APIs to use all features of Petal Search capabilities. It helps developers to integrate mobile app search experience into their application.
Huawei Search Kit offers to developers so much different and helpful features. It decreases our development cost with SDKs and APIs, it returns responses quickly and it helps us to develop our application faster.
As a developer, we have some responsibilities and function restrictions while using Huawei Search Kit. If you would like to learn about these responsibilities and function restrictions, I recommend you to visit following website.
https://developer.huawei.com/consum.../HMSCore-Guides/introduction-0000001055591730
Also, Huawei Search Kit supports limited countries and regions. If you wonder about these countries and regions, you can visit the following website.
https://developer.huawei.com/consum...HMSCore-Guides-V5/regions-0000001056871703-V5
How to use Huawei Search Kit?
First of all, we need to create an app on AppGallery Connect and add related details about HMS Core to our project.
If you don’t know about how to integrate HMS Core to our project, you can learn all details from following Medium article.
https://medium.com/huawei-developers/android-integrating-your-apps-with-huawei-hms-core-1f1e2a090e98
After we have done all steps in above Medium article, we can focus on special steps of integrating Huawei Search Kit.
Our minSdkVersion should be 24 at minimum.
We need to add following dependency to our app level build.gradle file.
Code:
implementation "com.huawei.hms:searchkit:5.0.4.303"
Then, we need to do some changes on AppGallery Connect. We need to define a data storage location on AppGallery Connect.
Note: If we don’t define a data storage location, all responses will return null.
We need to initialize the SearchKit instance on our application which we have extended from android.app.Application class. To initialize the SearchKit instance, we need to set the app id on second parameter which has mentioned as Constants.APP_ID.
While adding our application class to AndroidManifest.xml file, we need to set android:usesCleartextTraffic as true. You can do all these steps as mentioned in red rectangles.
Getting Access Token
For each request on Search Kit, we need to use access token. I prefer to get this access token on splash screen of the application. Thus, we will be able to save access token and save it with SharedPreferences.
First of all, we need to create our methods and objects about network operations. I am using Koin Framework for dependency injection on this project.
For creating objects about network operations, I have created following single objects and methods.
Note: In above picture, I have initialized the koin framework and added network module. Check this step to use this module in the app.
Java:
val networkModule = module {
single { getOkHttpClient(androidContext()) }
single { getRetrofit(get()) }
single { getService<AccessTokenService>(get()) }
}
fun getRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder().baseUrl("https://oauth-login.cloud.huawei.com/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
fun getOkHttpClient(context: Context): OkHttpClient {
return OkHttpClient().newBuilder()
.sslSocketFactory(SecureSSLSocketFactory.getInstance(context), SecureX509TrustManager(context))
.hostnameVerifier(StrictHostnameVerifier())
.readTimeout(10, TimeUnit.SECONDS)
.connectTimeout(1, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build()
}
inline fun <reified T> getService(retrofit: Retrofit): T = retrofit.create(T::class.java)
We have defined methods to create OkHttpClient and Retrofit objects. These objects have used as single to create Singleton objects. Also, we have defined one generic method to use Retrofit with our services.
To get an access token, our base URL will be “https://oauth-login.cloud.huawei.com/".
To get response from access token request, we need to define an object for response. The best way to do that is creating data class which is as shown in the below.
Java:
data class AccessTokenResponse(
@SerializedName("access_token") val accessToken: String?,
@SerializedName("expires_in") val expiresIn: Int?,
@SerializedName("token_type") val tokenType: String?
)
Now, all we need to do is, creating an interface to send requests with Retrofit. To get access token, our total URL is “https://oauth-login.cloud.huawei.com/oauth2/v3/token". We need to send 3 parameters as x-www-form-url encoded. Let’s examine these parameters.
grant_type: This parameter will not change depends on our application. Value should be, “client_credentials”.
client_id: This parameter will be app id of our project.
client_secret: This parameter will be app secret of our project.
Java:
interface AccessTokenService {
@FormUrlEncoded
@POST("oauth2/v3/token")
fun getAccessToken(
@Field("grant_type") grantType: String,
@Field("client_id") appId: String,
@Field("client_secret") clientSecret: String
): Call<AccessTokenResponse>
}
Now, everything is ready to get an access token. We just need to send the request and save the access token with SharedPreferences.
To work with SharedPreferences, I have created a helper class as shown in the below.
Java:
class CacheHelper {
companion object {
private lateinit var instance: CacheHelper
private var gson: Gson = Gson()
private const val PREFERENCES_NAME = BuildConfig.APPLICATION_ID
private const val PREFERENCES_MODE = AppCompatActivity.MODE_PRIVATE
fun getInstance(context: Context): CacheHelper {
instance = CacheHelper(context)
return instance
}
}
private var context: Context
private var sharedPreferences: SharedPreferences
private var sharedPreferencesEditor: SharedPreferences.Editor
private constructor(context: Context) {
this.context = context
sharedPreferences = this.context.getSharedPreferences(PREFERENCES_NAME, PREFERENCES_MODE)
sharedPreferencesEditor = sharedPreferences.edit()
}
fun putObject(key: String, `object`: Any) {
sharedPreferencesEditor.apply {
putString(key, gson.toJson(`object`))
commit()
}
}
fun <T> getObject(key: String, `object`: Class<T>): T? {
return sharedPreferences.getString(key, null)?.let {
gson.fromJson(it, `object`)
} ?: kotlin.run {
null
}
}
}
With the help of this class, we will be able to work with SharedPreferences easier.
Now, all we need to do it, sending request and getting access token.
Java:
object SearchKitService: KoinComponent {
private val accessTokenService: AccessTokenService by inject()
private val cacheHelper: CacheHelper by inject()
fun initAccessToken(requestListener: IRequestListener<Boolean, Boolean>) {
accessTokenService.getAccessToken(
"client_credentials",
Constants.APP_ID,
Constants.APP_SECRET
).enqueue(object: retrofit2.Callback<AccessTokenResponse> {
override fun onResponse(call: Call<AccessTokenResponse>, response: Response<AccessTokenResponse>) {
response.body()?.accessToken?.let { accessToken ->
cacheHelper.putObject(Constants.ACCESS_TOKEN_KEY, accessToken)
requestListener.onSuccess(true)
} ?: kotlin.run {
requestListener.onError(true)
}
}
override fun onFailure(call: Call<AccessTokenResponse>, t: Throwable) {
requestListener.onError(false)
}
})
}
}
If API returns as access token successfully, we will save this access token to device using SharedPreferences. And on our SplashFragment, we need to listen IRequestListener and if onSuccess method returns true, that means we got the access token successfully and we can navigate application to BrowserFragment.
Huawei Search Kit
In this article, I will give examples about News Search, Image Search and Video Search features of Huawei Search Kit.
In this article, I will give examples about News Search, Image Search and Video Search features of Huawei Search Kit.
To send requests for News Search, Image Search and Video Search, we need a CommonSearchRequest object.
In this app, I will get results about Corona in English. I have created the following method to return to CommonSearchRequest object.
Java:
private fun returnCommonRequest(): CommonSearchRequest {
return CommonSearchRequest().apply {
setQ("Corona Virus")
setLang(Language.ENGLISH)
setSregion(Region.WHOLEWORLD)
setPs(20)
setPn(1)
}
}
Here, we have setted some informations. Let’s examine this setter methods.
setQ(): Setting the keyword for search.
setLang(): Setting the language for search. Search Kit has it’s own model for language. If you would like examine this enum and learn about which Languages are supporting by Search Kit, you can visit the following website.
Huawei Search Kit — Language Model
setSregion(): Setting the region for search. Search Kit has it’s own model for region. If you would like examine this enum and learn about which Regions are supporting by Search Kit, you can visit the following website.
Huawei Search Kit — Region Model
setPn(): Setting the number about how much items will be in current page. The value ranges from 1 to 100, and the default value is 1.
setPs(): Setting the number of search results that will be returned on a page. The value ranges from 1 to 100, and the default value is 10.
Now, all we need to do is getting news, images, videos and show the results for these on the screen.
News Search
To get news, we can use the following method.
Java:
fun newsSearch(requestListener: IRequestListener<List<NewsItem>, String>) {
SearchKitInstance.getInstance().newsSearcher.setCredential(SearchKitService.accessToken)
var newsList = SearchKitInstance.getInstance().newsSearcher.search(SearchKitService.returnCommonRequest())
newsList?.getData()?.let { newsItems ->
requestListener.onSuccess(newsItems)
} ?: kotlin.run {
requestListener.onError("No value returned")
}
}
Image Search
To get images, we can use the following method.
Java:
fun imageSearch(requestListener: IRequestListener<List<ImageItem>, String>) {
SearchKitInstance.getInstance().imageSearcher.setCredential(SearchKitService.accessToken)
var imageList = SearchKitInstance.getInstance().imageSearcher.search(SearchKitService.returnCommonRequest())
imageList?.getData()?.let { imageItems ->
requestListener.onSuccess(imageItems)
} ?: kotlin.run {
requestListener.onError("No value returned")
}
}
Video Search
To get images, we can use the following method.
Java:
fun videoSearch(requestListener: IRequestListener<List<VideoItem>, String>) {
SearchKitInstance.getInstance().videoSearcher.setCredential(SearchKitService.accessToken)
var videoList = SearchKitInstance.getInstance().videoSearcher.search(SearchKitService.returnCommonRequest())
videoList?.getData()?.let { videoList ->
requestListener.onSuccess(videoList)
} ?: kotlin.run {
requestListener.onError("No value returned")
}
}
Showing on screen
All these results return a clickable url for each one. We can create an intent to open these URLs on the browser which has installed to device before.
To do that and other operations, I will share BrowserFragment codes for fragment and the SearchItemAdapter codes for recyclerview.
Java:
class BrowserFragment: Fragment() {
private lateinit var viewBinding: FragmentBrowserBinding
private lateinit var searchOptionsTextViews: ArrayList<TextView>
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
viewBinding = FragmentBrowserBinding.inflate(inflater, container, false)
searchOptionsTextViews = arrayListOf(viewBinding.news, viewBinding.images, viewBinding.videos)
return viewBinding.root
}
private fun setListeners() {
viewBinding.news.setOnClickListener { getNews() }
viewBinding.images.setOnClickListener { getImages() }
viewBinding.videos.setOnClickListener { getVideos() }
}
private fun getNews() {
SearchKitService.newsSearch(object: IRequestListener<List<NewsItem>, String>{
override fun onSuccess(newsItemList: List<NewsItem>) {
setupRecyclerView(newsItemList, viewBinding.news)
}
override fun onError(errorMessage: String) {
Toast.makeText(requireContext(), errorMessage, Toast.LENGTH_SHORT).show()
}
})
}
private fun getImages(){
SearchKitService.imageSearch(object: IRequestListener<List<ImageItem>, String>{
override fun onSuccess(imageItemList: List<ImageItem>) {
setupRecyclerView(imageItemList, viewBinding.images)
}
override fun onError(errorMessage: String) {
Toast.makeText(requireContext(), errorMessage, Toast.LENGTH_SHORT).show()
}
})
}
private fun getVideos() {
SearchKitService.videoSearch(object: IRequestListener<List<VideoItem>, String>{
override fun onSuccess(videoItemList: List<VideoItem>) {
setupRecyclerView(videoItemList, viewBinding.videos)
}
override fun onError(errorMessage: String) {
Toast.makeText(requireContext(), errorMessage, Toast.LENGTH_SHORT).show()
}
})
}
private val clickListener = object: IClickListener<String> {
override fun onClick(clickedInfo: String) {
var intent = Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse(clickedInfo)
}
startActivity(intent)
}
}
private fun <T> setupRecyclerView(itemList: List<T>, selectedSearchOption: TextView) {
viewBinding.searchKitRecyclerView.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = SearchItemAdapter<T>(itemList, clickListener)
}
changeSelectedTextUi(selectedSearchOption)
}
private fun changeSelectedTextUi(selectedSearchOption: TextView) {
for (textView in searchOptionsTextViews)
if (textView == selectedSearchOption) {
textView.background = requireContext().getDrawable(R.drawable.selected_text)
} else {
textView.background = requireContext().getDrawable(R.drawable.unselected_text)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setListeners()
getNews()
}
}
Java:
class SearchItemAdapter<T>(private val searchItemList: List<T>,
private val clickListener: IClickListener<String>):
RecyclerView.Adapter<SearchItemAdapter.SearchItemHolder<T>>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchItemHolder<T> {
val itemBinding = ItemSearchBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return SearchItemHolder<T>(itemBinding)
}
override fun onBindViewHolder(holder: SearchItemHolder<T>, position: Int) {
val item = searchItemList[position]
var isLast = (position == searchItemList.size - 1)
holder.bind(item, isLast, clickListener)
}
override fun getItemCount(): Int = searchItemList.size
override fun getItemViewType(position: Int): Int = position
class SearchItemHolder<T>(private val itemBinding: ItemSearchBinding): RecyclerView.ViewHolder(itemBinding.root) {
fun bind(item: T, isLast: Boolean, clickListener: IClickListener<String>) {
if (isLast)
itemBinding.itemSeparator.visibility = View.GONE
lateinit var clickUrl: String
var imageUrl = "https://www.who.int/images/default-source/infographics/who-emblem.png?sfvrsn=877bb56a_2"
when(item){
is NewsItem -> {
itemBinding.searchResultTitle.text = item.title
itemBinding.searchResultDetail.text = item.provider.siteName
clickUrl = item.clickUrl
item.provider.logo?.let { imageUrl = it }
}
is ImageItem -> {
itemBinding.searchResultTitle.text = item.title
clickUrl = item.clickUrl
item.sourceImage.image_content_url?.let { imageUrl = it }
}
is VideoItem -> {
itemBinding.searchResultTitle.text = item.title
itemBinding.searchResultDetail.text = item.provider.siteName
clickUrl = item.clickUrl
item.provider.logo?.let { imageUrl = it }
}
}
itemBinding.searchItemRoot.setOnClickListener {
clickListener.onClick(clickUrl)
}
getImageFromUrl(imageUrl, itemBinding.searchResultImage)
}
private fun getImageFromUrl(url: String, imageView: ImageView) {
Glide.with(itemBinding.root)
.load(url)
.centerCrop()
.into(imageView);
}
}
}
End
If you would like to learn more about Search Kit and see the Codelab, you can visit the following websites:
https://developer.huawei.com/consum.../HMSCore-Guides/introduction-0000001055591730
https://developer.huawei.com/consumer/en/codelab/HMSSearchKit/index.html#0
Very nice guide.
Amazing.
Thank you very much
It's a very nice example.

Make Your Apps More Secure with the Safety SDK

Hi everyone,
In this article, I will talk about the security SDK development in a single code base structure for Huawei Safety Detect and Google Safety Net services, which will make your applications more secure. Thanks to this SDK, Huawei Safety Detect service with HMS (Huawei Mobile Services) and stages with GMS (Google Mobile Services) can be run compatible with Google Safety Net package for 2 platforms.
{
"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"
}
Within the scope of the SDK, we will include the following features;
User Detect: With this feature, you can make your application more secure by checking whether the users using our application are fake users. This feature is a very important and frequently used feature for banks and many e-commerce applications.
Root Detection: With this feature, it allows you to make the application more secure by checking whether the device running the application is a rooted device. It is critically important, especially for applications in the banking industry.
Huawei Safety Detect Service​Huawei Safety Detect service, as I mentioned at the beginning of my article, is a security service that allows you to make your applications more secure and protect against security threats.You can find detailed information about Huawei Safety Detect service here.
Google Safety Net Service​Safety Net is a service that provides a set of services and APIs that help protect your app against security threats, including bad URLs, potentially harmful apps, and rogue users.You can find detailed information about the Google Safety Net service here.
Adding a Module
First, let’s create a new module where we will do all the improvements. Let’s create a new module by choosing File -> New -> New Module -> Android Library and name it safety. After this step, we need to add dependencies to the build.gradle file of our module.
build.gradle(safety)
Code:
implementation 'com.huawei.hms:safetydetect:5.0.5.302'
implementation 'com.google.android.gms:play-services safetynet:17.0.0'
After creating a new module and adding the necessary dependencies, we can now start SDK development.
First of all, we create our interface, which contains the functions that we will use jointly for both platforms (Google-Huawei). Next, we will create the Device class, which will allow us to find the Mapper class and the mobile service type installed on the device.
Code:
interface SafetyService {
fun userDetect(appKey : String,callback: SafetyServiceCallback<SafetyServiceResponse>)
fun rootDetection(appKey: String,callback: SafetyRootDetectionCallback<RootDetectionResponse> )
interface SafetyServiceCallback<T>{
fun onSuccessUserDetect(result: T? = null)
fun onFailUserDetect(e: java.lang.Exception)
}
interface SafetyRootDetectionCallback<T>{
fun onSuccessRootDetect(result: T? = null)
fun onFailRootDetect(e: java.lang.Exception)
}
object Factory {
fun create(context: Context): SafetyService {
return when (Device.getMobileServiceType(context)) {
MobileServiceType.GMS -> {
GoogleSafetyServiceImpl(context)
}
MobileServiceType.HMS -> {
HuaweiSafetyServiceImpl(context)
}
else -> {
throw Exception("Unknown service")
}
}
}
}
}
As seen above, we create methods and callbacks that will perform both user detect and root detection. Then, in the create method, we check the service availability on the device from the Device class and work the Google or Huawei SafetyServiceImpl classes.
Code:
enum class MobileServiceType {
HMS,
GMS,
NON
}
object Device {
/**
* Mobile services availability of devices
*
* @return Device mobile service type enum
*/
fun getMobileServiceType(
context: Context,
firstPriority: MobileServiceType? = null
): MobileServiceType {
val gms: Boolean = GoogleApiAvailability.getInstance()
.isGooglePlayServicesAvailable(context) == com.google.android.gms.common.ConnectionResult.SUCCESS
val hms: Boolean = HuaweiApiAvailability.getInstance()
.isHuaweiMobileServicesAvailable(context) == com.huawei.hms.api.ConnectionResult.SUCCESS
return if (gms && hms) {
firstPriority ?: MobileServiceType.HMS
} else if (gms) {
MobileServiceType.GMS
} else if (hms) {
MobileServiceType.HMS
} else {
MobileServiceType.NON
}
}
After these operations, we must create the Mapper class in order to parse the objects that we send and receive from the services as parameters, and the other classes we need to define.
Code:
abstract class Mapper<I, O> {
abstract fun map(from: I): O
}
After this step, we must define separate Mapper classes for Google and Huawei. For example, as a result of user detect operation, responseToken object is returned to us as Google Safety Net API and Huawei Safety Detect service return parameter. Thanks to our mapper class, we will be able to parse it into our response class, which we will create when it returns from Google or Huawei service.
Code:
class GoogleSafetyMapper: Mapper<SafetyNetApi.RecaptchaTokenResponse, SafetyServiceResponse>() {
override fun map(from: SafetyNetApi.RecaptchaTokenResponse): SafetyServiceResponse = SafetyServiceResponse(
responseToken = from.tokenResult
)
}
Code:
class HuaweiSafetyMapper : Mapper<UserDetectResponse, SafetyServiceResponse>() {
override fun map(from: UserDetectResponse): SafetyServiceResponse = SafetyServiceResponse(
responseToken = from.responseToken
)
}
As seen in the codes above, Huawei Safety Detect service returns UserDetectResponse and Google Safety Net service returns RecaptchaTokenResponse objects for the result of user detect operation. We will return the SafetyServiceResponse object in our SDK.
Code:
data class SafetyServiceResponse(
val responseToken: String
)
We should do the same for the root detection feature. We will create our RootDetectionResponse class, which will enable us to parse objects returned from Google or Huawei service by creating mapper classes for root detection.
Code:
data class RootDetectionResponse(
val apkDigestSha256: String,
val apkPackageName: String,
val basicIntegrity: Boolean,
val nonce: String,
val timestampMs: Long
)
Next we need to create our mapper classes for Google and Huawei. SafetyNet and Safety Detect services return Json object as response parameter. Here, instead of sending a json object, we will use the parsed version of the json object in our SDK.
Code:
class GoogleRootDetectMapper : Mapper<JSONObject,RootDetectionResponse>() {
override fun map(from: JSONObject): RootDetectionResponse = RootDetectionResponse(
apkDigestSha256 = from.getString("apkDigestSha256"),
apkPackageName = from.getString("apkPackageName"),
basicIntegrity = from.getBoolean("basicIntegrity"),
nonce = from.getString("nonce"),
timestampMs = from.getLong("timestampMs")
)
}
Code:
class HuaweiRootDetectMapper : Mapper<JSONObject, RootDetectionResponse>(){
override fun map(from: JSONObject): RootDetectionResponse = RootDetectionResponse(
apkDigestSha256 = from.getString("apkDigestSha256"),
apkPackageName = from.getString("apkPackageName"),
basicIntegrity = from.getBoolean("basicIntegrity"),
nonce = from.getString("nonce"),
timestampMs = from.getLong("timestampMs")
)
}
After all these steps, we will now create our SafetyServiceImpl classes, which we will implement our interface and add functionality to our functions. It must be created separately for both Google and Huawei.
Code:
class GoogleSafetyServiceImpl(private val context: Context): SafetyService {
private val mapper: Mapper<SafetyNetApi.RecaptchaTokenResponse, SafetyServiceResponse> = GoogleSafetyMapper()
private val rootDetectMapper: Mapper<JSONObject, RootDetectionResponse> = GoogleRootDetectMapper()
override fun userDetect(appKey: String,callback: SafetyService.SafetyServiceCallback<SafetyServiceResponse>){
/**
* App key value is the SITE_API_KEY value in Google Mobile Services.
*/
SafetyNet.getClient(context).verifyWithRecaptcha(appKey)
.addOnSuccessListener(){
val responseToken = it.tokenResult
if(responseToken.isNotEmpty()){
callback.onSuccessUserDetect(mapper.map(it))
}
}.addOnFailureListener(){
callback.onFailUserDetect(it)
}
}
override fun rootDetection(
appKey: String,
callback: SafetyService.SafetyRootDetectionCallback<RootDetectionResponse>
){
val nonce = ByteArray(24)
try {
val random: SecureRandom = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
SecureRandom.getInstanceStrong()
} else {
SecureRandom.getInstance("SHA1PRNG")
}
random.nextBytes(nonce)
} catch (e: NoSuchAlgorithmException) {
Log.e(TAG, e.message!!)
}
SafetyNet.getClient(context).attest(nonce, appKey)
.addOnSuccessListener{ result ->
val jwsStr = result.jwsResult
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)
val jsonObject = JSONObject(payloadDetail)
callback.onSuccessRootDetect(rootDetectMapper.map(jsonObject))
}.addOnFailureListener{ e->
callback.onFailRootDetect(e)
}
}
}
As can be seen in our SafetyServiceImpl class created for the Google side above, functionality has been added to the user detection and root detection methods by implementing the methods we created in the interface. In cases where there is onSuccess() in user detect and root detection processes, we transfer the response returned from the SafetyNet API to our own response class with mapper, thanks to the callbacks we created in our interface. As a result of this process, objects returned from services are transferred to our response class that we created in our SDK.
The important issue here is that the appKey value is different in Google and Huawei services. It corresponds to the SITE_API_KEY value on the Google side. The SITE_API_KEY value needs to be generated by the reCAPTCHA API Console. Thanks to this console, you can prevent risky login attempts in your application, etc. You can track many metrics.
For the Huawei side, we should also create our Huawei Safety ServiceImpl class.
Code:
class HuaweiSafetyServiceImpl(private val context: Context): SafetyService {
private val mapper: Mapper<UserDetectResponse, SafetyServiceResponse> = HuaweiSafetyMapper()
private val rootDetectMapper: Mapper<JSONObject, RootDetectionResponse> = HuaweiRootDetectMapper()
val TAG = "CommonMobileServicesSafetySDK"
/**
App key value is the app_id value in Huawei Mobile Services.
*/
override fun userDetect(
appKey: String,
callback: SafetyService.SafetyServiceCallback<SafetyServiceResponse>
){
val client = SafetyDetect.getClient(context)
client.userDetection(appKey).addOnSuccessListener {
val responseToken = it.responseToken
if(responseToken.isNotEmpty()){
callback.onSuccessUserDetect(mapper.map(it))
}
}.addOnFailureListener {
callback.onFailUserDetect(it)
}
}
@SuppressLint("LongLogTag")
override fun rootDetection(
appKey: String,
callback: SafetyService.SafetyRootDetectionCallback<RootDetectionResponse>
) {
val nonce = ByteArray(24)
try {
val random: SecureRandom = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
SecureRandom.getInstanceStrong()
} else {
SecureRandom.getInstance("SHA1PRNG")
}
random.nextBytes(nonce)
} catch (e: NoSuchAlgorithmException) {
Log.e(TAG, e.message!!)
}
SafetyDetect.getClient(context)
.sysIntegrity(nonce, appKey)
.addOnSuccessListener { result ->
val jwsStr = result.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)
val jsonObject = JSONObject(payloadDetail)
callback.onSuccessRootDetect(rootDetectMapper.map(jsonObject))
}
.addOnFailureListener { e ->
callback.onFailRootDetect(e)
}
}
}
In the Huawei Safety Detect service, the appKey value corresponds to the appId value.
On the root detection side, the SITE_API_KEY value is created from the Google API Console. In the Huawei Safety service, it also corresponds to the appId value.
After all these steps, we have completed the developments on the SDK side. After implementing the SDK in a different project so that we can test it, you can use it as follows.
Code:
private var safetyService = SafetyService.Factory.create(requireContext())
appKey = if(Device.getMobileServiceType(requireContext())== MobileServiceType.GMS){
this.getString(R.string.google_site_api_key)
}
else{
this.getString(R.string.app_id)
}
safetyService?.userDetect(appKey, object : SafetyService.SafetyServiceCallback<SafetyServiceResponse> {
override fun onFailUserDetect(e: Exception) {
Toast.makeText(requireContext(), e.toString(), Toast.LENGTH_SHORT).show()
}
override fun onSuccessUserDetect(result: SafetyServiceResponse?) {
viewModel.signInWithEmail(email, password)
}
})
safetyService?.rootDetection(appKey, object : SafetyService.SafetyRootDetectionCallback<RootDetectionResponse> {
override fun onFailRootDetect(e: Exception) {
Toast.makeText(applicationContext,e.toString(),Toast.LENGTH_SHORT).show()
}
override fun onSuccessRootDetect(result: RootDetectionResponse?) {
if(result!= null){
if(result.basicIntegrity){
showSecurityAlertMessage(getString(R.string.root_device_info),"Info",true)
}
else{
showSecurityAlertMessage(getString(R.string.no_root_device_error),"Security Warning",false)
}
}
}
})
As seen in the example code above, we set our appKey value according to the service availability on the device, thanks to the Device class we created in the SDK. Here, if the device is GMS, we set the API_KEY values that we created from Google reCaptcha and Api Console, and set the appId value to the appKey value if it is HMS. We can easily use the user detect and root detection features by calling the methods we will use in the next step, thanks to the interface we created in our SDK.
You can find screenshots of user detect and root detect features of a different application using the SDK.
Tips and Tricks
During SDK development, all common methods should be handled by interfaces.
App key value is app id for Huawei services, SITE_API_KEY value generated from Google Api Console for Google services.
Conclusion
In this article, I tried to explain how the services used for security in Google and Huawei services can be developed by making them compatible with both GMS and HMS devices and combining them under a single SDK. I hope it was a useful article for everyone. Thank you for taking the time to read.
References
Google Safety Net API
Huawei Safety Detect Service
Original Source

Categories

Resources