{
"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"
}
Article Introduction
Validating identity is incredibly important in mobile development, and assuring new user signups are real human beings is critical. Developers need reliable ways to confirm the identities of their users to prevent security issues. Developers can implement verification systems using phone numbers in two main ways: either by calling or by sending an SMS containing a code that the user must input. In this article, we’ll implement SMS Retriever API to support both GMS and HMS, so our app can read the Verification/OTP SMS and verify the user automatically.
Automatic and one-tap SMS verification
Verify your users by SMS without making them deal with verification codes. If your app requires a user to enter a mobile number and verifies the user identity using an SMS verification code, you can integrate the ReadSmsManager service so that your app can automatically read the SMS verification code without applying for the SMS reading permission. After the integration, SMS verification codes are automatically filled in for verification, greatly improving user experience.
The process is described as follows:
1. HMS Core (APK) sends the SMS message that meets the rules to your app through a directed broadcast.
2. Your app receives the directed broadcast, parses it to obtain the SMS verification code, and displays it on your app.
3. The user checks whether the verification code is correct and if so, sends a verification request.
4. Your app sends the verification code from the user to your app server for check.
5. Your app server checks whether the verification code is correct and if so, returns the verification result to your app.
Pre-Requisites
ReadSmsManager Support the following:
Support
Value
Devices
Phones and Tablets
Operating System
EMUI 3.0 or later
Android
Android 4.4 or later
SMS message rules
SMS Template:
prefix_flag short message verification code is XXXXXX hash_value
prefix_flag
indicates the prefix of an SMS message, which can be <#>, [#], or \u200b\u200b. \u200b\u200b is invisible Unicode characters.
short message verification code is
indicates the SMS message content, which you can define as needed.
XXXXXX
indicates the verification code.
hash_value
indicates the hash value generated by the HMS Core SDK based on the package name of an app to uniquely identify the app
Step 1: Get Hash Value:
You get your hash value by implementing the following class:
Java:
public class hashcodeHMS extends ContextWrapper {
public static final String TAG = hashcodeHMS.class.getSimpleName();
public hashcodeHMS(Context context) {
super(context);
}
public MessageDigest getMessageDigest() {
MessageDigest messageDigest = null;
try {
messageDigest = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "No Such Algorithm.", e);
}
return messageDigest;
}
public String getSignature(Context context, String packageName) {
PackageManager packageManager = context.getPackageManager();
Signature[] signatureArrs;
try {
signatureArrs = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES).signatures;
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Package name inexistent.");
return "";
}
if (null == signatureArrs || 0 == signatureArrs.length) {
Log.e(TAG, "signature is null.");
return "";
}
Log.e("hashhms =>", signatureArrs[0].toCharsString());
return signatureArrs[0].toCharsString();
}
public String getHashCode(String packageName, MessageDigest messageDigest, String signature) {
String appInfo = packageName + " " + signature;
messageDigest.update(appInfo.getBytes(StandardCharsets.UTF_8));
byte[] hashSignature = messageDigest.digest();
hashSignature = Arrays.copyOfRange(hashSignature, 0, 9);
String base64Hash = Base64.encodeToString(hashSignature, Base64.NO_PADDING | Base64.NO_WRAP);
base64Hash = base64Hash.substring(0, 11);
return base64Hash;
}
}
Step 2: Start the consent from SMS Manager:
Code:
val task = ReadSmsManager.startConsent([email protected], null)
task.addOnCompleteListener { it ->
if (it.isSuccessful) {
// The service is enabled successfully. Perform other operations as needed.
tv_title.text = "Waiting for the OTP"
Toast.makeText(this, "SMS Retriever starts", Toast.LENGTH_LONG).show()
}else{
tv_title.text = "ReadSmsManager did not worked"
Toast.makeText(this, "SMS Retriever did not start", Toast.LENGTH_LONG).show()
}
}
Step 3: Prepare your Broadcast Receiver:
Java:
class MySMSBrodcastReceiverHms : BroadcastReceiver() {
private var otpReceiver: OTPReceiveListenerHMS? = null
fun initOTPListener(receiver: OTPReceiveListenerHMS) {
this.otpReceiver = receiver
Log.e("Firas", "initOTPListener: Done", )
}
override fun onReceive(context: Context, intent: Intent) {
intent?.let { it ->
val bundle = it.extras
bundle?.let { itBundle ->
if (ReadSmsConstant.READ_SMS_BROADCAST_ACTION == it.action) {
val status : Status? = itBundle.getParcelable(ReadSmsConstant.EXTRA_STATUS)
if (status?.statusCode == CommonStatusCodes.TIMEOUT) {
// The service has timed out and no SMS message that meets the requirements is read. The service process ends.
Log.i("Firas", "onReceive: TIMEOUT")
otpReceiver!!.onOTPTimeOutHMS()
}else if (status?.statusCode == CommonStatusCodes.SUCCESS){
if (bundle.containsKey(ReadSmsConstant.EXTRA_SMS_MESSAGE)) {
// An SMS message that meets the requirement is read. The service process ends.
var otp: String = bundle.getString(ReadSmsConstant.EXTRA_SMS_MESSAGE) as String
Log.i("Firas", "onReceive: ${otp}")
otp = otp.replace("[#] short message verification code is ", "").split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0]
otp = otp.split(" ").dropLastWhile { it.isEmpty() }.toTypedArray()[0]
Log.i("Firas", "onReceive: ${otp}")
otpReceiver!!.onOTPReceivedHMS(otp)
}
}
}
}
}
}
interface OTPReceiveListenerHMS {
fun onOTPReceivedHMS(otp: String)
fun onOTPTimeOutHMS()
}
}
*Add the broadcast receiver to the manifest*
XML:
<receiver
android:name=".MySMSBrodcastReceiverHms"
android:exported="true">
<intent-filter>
<action android:name="com.huawei.hms.support.sms.common.ReadSmsConstant.READ_SMS_BROADCAST_ACTION" />
</intent-filter>
</receiver>
Step 4: Prepare your Broadcast Receiver:
Implement the OTPReceiverListenerHMS interface from our MySMSBrodcastReceiverHms:
Code:
* class otp_read : AppCompatActivity(),MySMSBrodcastReceiverHms.OTPReceiveListenerHMS{}*
Then create and initiate the OTPListener
Code:
var smsBroadcast = MySMSBrodcastReceiverHms() as MySMSBrodcastReceiverHms
(smsBroadcast as MySMSBrodcastReceiverHms).initOTPListener(this)
Implement the functions of the interface:
Code:
override fun onOTPReceivedHMS(otp: String) {
Toast.makeText(this, " onOTPReceived", Toast.LENGTH_SHORT).show()
if (smsBroadcast != null) {
LocalBroadcastManager.getInstance(this).unregisterReceiver(smsBroadcast as MySMSBrodcastReceiverHms)
}
Toast.makeText(this, otp, Toast.LENGTH_SHORT).show()
tv_title.text = "$otp"
otp_view.setText(otp)
Log.e("OTP Received", otp)
}
override fun onOTPTimeOutHMS() {
tv_title.setText("Timeout")
Toast.makeText(this, " SMS retriever API Timeout", Toast.LENGTH_SHORT).show()
}
Step 5: Register your receiver:
Code:
val intentFilter = IntentFilter()
intentFilter.addAction(ReadSmsConstant.READ_SMS_BROADCAST_ACTION)
applicationContext.registerReceiver(
smsBroadcast as MySMSBrodcastReceiverHms,
intentFilter
)
Step 6: Run the application
Conclusion
This feature will help the user to be verified faster than the regular way and it will prevent the user from any typo mistake like writing the OTP, keep in mind this feature will work only if the user has the sim card on his phone other wais it will not trigger the broadcast receiver. From now not every app with READ SMS permission can access your personal data like messages. Usually, to auto-fill OTP we give access to an android app it’s better to off that permission after the process is completed (else they will have access to each & every message you have on the mobile), but how many will do that. With SMS Retriever API apps won’t ask for READ SMS permission to auto-fill OTP.
Tips & Tricks
Remove AppSignatureHelper class from your project before going to production.
Debug and Release APK’s might have different Hashcodes, make sure you get hashcode from release APK.
References
Automatically Reading an SMS Verification Code Without User Authorization:
https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/readsmsmanager-0000001050050861
Automatically Reading an SMS Verification Code After User Authorization:
https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/authotize-to-read-sms-0000001061481826#EN-US_TOPIC_0000001126286229__section1186673334918
ReadSmsManager Reference:
https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/readsmsmanager-0000001050050861
Obtaining the Hash Value Reference:
https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides-V5/obtaining-hash-value-0000001050194405-V5
Checkout in forum
Related
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?
Hi everyone! Today I will briefly walkthrough you about how to implement an SMS Retriever system using Huawei’s SMSRetriever service.
{
"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"
}
First of all, let’s start with why do we need this service in our apps. Applications that involve user verification usually do this with an SMS code, and this SMS code is entered to a field in the app to verify user account. These sms codes are named as One-Time-Password, OTP for short. OTP’s can be used for verification of real user or increasing account security. Our purpose here is to retrieve this code and automatically enter it to the necessary field in our app. So let’s begin!
Our steps through the guide will be as:
Complete HMS Service implementation
1 — Require SMS read permission
2 — Initiate ReadSmsManager
3 — Register broadcast receiver for SMS
4 — Send SMS for user
5 — Register broadcast receiver for OTP — to extract code from SMS —
6 — Create SMSBroadcastReceiver class
1 — We begin by adding our required permission to the AndroidManifest.xml
XML:
<uses-permission android:name="android.permission.SEND_SMS" />
Note: Don’t forget to check if user has given permission.
Code:
val permissionCheck = ContextCompat.checkSelfPermission(this, Manifest.permission.SEND_SMS)
if (permissionCheck == PackageManager.PERMISSION_GRANTED)
// Send sms
else
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.SEND_SMS), SEND_SMS_REQUEST_CODE)
2 — Then we add the ReadSmsManager to initiate our SMS service.
Before ReadSmsManager code snippet, we have to add Huawei Account Kit implementation.
Code:
implementation 'com.huawei.hms:hwid:5.1.0.301'
After Gradle sync, we can add the SMS manager to our class.
Code:
val task = ReadSmsManager.startConsent([email protected], mobileNumber)
task.addOnCompleteListener {
if (task.isSuccessful) {
Toast.makeText(this, "Verification code sent successfully", Toast.LENGTH_LONG).show()
} else
Toast.makeText(this, "Task failed.", Toast.LENGTH_LONG).show()
}
3 — For SMS broadcast receiver, we need to add this little code block.
Code:
val intentFilter = IntentFilter(READ_SMS_BROADCAST_ACTION)
registerReceiver(SmsBroadcastReceiver(), intentFilter)
Note: After you add this code block, you will get an error saying SmsBroadcastReceiver cannot be found, because we didn’t define it yet. We are just getting our SmsActivity ready. We will be adding it once we are done here.
4 — After we register our receiver, we can send the SMS to our user.
Code:
val smsManager = SmsManager.getDefault()
smsManager.sendTextMessage(
mobileNumber,
null,
"Your verification code is $otp",
null,
null
)
Note: here otp will cause an error as it is not defined anywhere yet. You should implement a random OTP generator fitting your likings and assign the value to otp .
5 — Now that we sent an SMS to user, we should register the broadcast receiver to be able to retrieve the code from it.
Code:
val filter = IntentFilter()
filter.addAction("service.to.activity.transfer")
val otpReceiver = object : BroadcastReceiver() {
override fun onReceive(
context: Context,
intent: Intent
) {
intent.getStringExtra("sms")?.let { data ->
// You should find your otp code here in `data`
}
}
}
registerReceiver(otpReceiver, filter)
Note: Once we complete our classes, you should be setting your otp value to your view. This part is left out in the code snippet as view bindings and data bindings may vary on projects.
6 —Finally, where we get to read the messages and find our code.
Code:
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.huawei.hms.common.api.CommonStatusCodes
import com.huawei.hms.support.api.client.Status
import com.huawei.hms.support.sms.common.ReadSmsConstant
class SmsBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val bundle = intent!!.extras
if (bundle != null) {
val status: Status? = bundle.getParcelable(ReadSmsConstant.EXTRA_STATUS)
if (status?.statusCode == CommonStatusCodes.TIMEOUT) {
// Process system timeout
} else if (status?.statusCode == CommonStatusCodes.SUCCESS) {
if (bundle.containsKey(ReadSmsConstant.EXTRA_SMS_MESSAGE)) {
bundle.getString(ReadSmsConstant.EXTRA_SMS_MESSAGE)?.let {
val local = Intent()
local.action = "service.to.activity.transfer"
local.putExtra("sms", it)
context!!.sendBroadcast(local)
}
}
}
}
}
}
So that’s it. You should be able to successfully register for SMS sending and retrieving, then read OTP from content and use in whatever way you like.
Thanks for reading through the guide and I hope it is simple and useful for you. Let me know if you have any suggestions or problems.
References
https://developer.huawei.com/consum...upport-sms-readsmsmanager-0000001050050553-V5
https://developer.huawei.com/consum...Guides/authotize-to-read-sms-0000001061481826
https://medium.com/huawei-developers/android-integrating-your-apps-with-huawei-hms-core-1f1e2a090e98
Does it support text based otp? and does it supports 6 and 8 digit number OTP?
Basavaraj.navi said:
Does it support text based otp? and does it supports 6 and 8 digit number OTP?
Click to expand...
Click to collapse
I haven't tried text based otp but I believe it supports. You can try
val otp = Random.nextInt(
100000, // six digit, 10000000 to 99999999 for 8 digit otp
999999
).toString()
for 6 and 8 digit otps.
Which permission required for getting the OTP from inbox?
ask011 said:
Which permission required for getting the OTP from inbox?
Click to expand...
Click to collapse
You need send_sms permission.
Is this service is free ?
Great help for different users indeed. should be appreciated. Indeed it will be a great initiative.
How app detects the OTP from msg application when OTP arrives?
ask011 said:
How app detects the OTP from msg application when OTP arrives?
Click to expand...
Click to collapse
Code:
val filter = IntentFilter()
filter.addAction("service.to.activity.transfer")
val otpReceiver = object : BroadcastReceiver() {
override fun onReceive(
context: Context,
intent: Intent
) {
intent.getStringExtra("sms")?.let { data ->
// You should find your otp code here in `data`
}
}
}
registerReceiver(otpReceiver, filter)
you should extract your otp here from 'data'.
ErtugSagman said:
Hi everyone! Today I will briefly walkthrough you about how to implement an SMS Retriever system using Huawei’s SMSRetriever service.
View attachment 5201489
First of all, let’s start with why do we need this service in our apps. Applications that involve user verification usually do this with an SMS code, and this SMS code is entered to a field in the app to verify user account. These sms codes are named as One-Time-Password, OTP for short. OTP’s can be used for verification of real user or increasing account security. Our purpose here is to retrieve this code and automatically enter it to the necessary field in our app. So let’s begin!
Our steps through the guide will be as:
Complete HMS Service implementation
1 — Require SMS read permission
2 — Initiate ReadSmsManager
3 — Register broadcast receiver for SMS
4 — Send SMS for user
5 — Register broadcast receiver for OTP — to extract code from SMS —
6 — Create SMSBroadcastReceiver class
View attachment 5201491
1 — We begin by adding our required permission to the AndroidManifest.xml
XML:
<uses-permission android:name="android.permission.SEND_SMS" />
Note: Don’t forget to check if user has given permission.
Code:
val permissionCheck = ContextCompat.checkSelfPermission(this, Manifest.permission.SEND_SMS)
if (permissionCheck == PackageManager.PERMISSION_GRANTED)
// Send sms
else
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.SEND_SMS), SEND_SMS_REQUEST_CODE)
2 — Then we add the ReadSmsManager to initiate our SMS service.
Before ReadSmsManager code snippet, we have to add Huawei Account Kit implementation.
Code:
implementation 'com.huawei.hms:hwid:5.1.0.301'
After Gradle sync, we can add the SMS manager to our class.
Code:
val task = ReadSmsManager.startConsent([email protected], mobileNumber)
task.addOnCompleteListener {
if (task.isSuccessful) {
Toast.makeText(this, "Verification code sent successfully", Toast.LENGTH_LONG).show()
} else
Toast.makeText(this, "Task failed.", Toast.LENGTH_LONG).show()
}
3 — For SMS broadcast receiver, we need to add this little code block.
Code:
val intentFilter = IntentFilter(READ_SMS_BROADCAST_ACTION)
registerReceiver(SmsBroadcastReceiver(), intentFilter)
Note: After you add this code block, you will get an error saying SmsBroadcastReceiver cannot be found, because we didn’t define it yet. We are just getting our SmsActivity ready. We will be adding it once we are done here.
4 — After we register our receiver, we can send the SMS to our user.
Code:
val smsManager = SmsManager.getDefault()
smsManager.sendTextMessage(
mobileNumber,
null,
"Your verification code is $otp",
null,
null
)
Note: here otp will cause an error as it is not defined anywhere yet. You should implement a random OTP generator fitting your likings and assign the value to otp .
5 — Now that we sent an SMS to user, we should register the broadcast receiver to be able to retrieve the code from it.
Code:
val filter = IntentFilter()
filter.addAction("service.to.activity.transfer")
val otpReceiver = object : BroadcastReceiver() {
override fun onReceive(
context: Context,
intent: Intent
) {
intent.getStringExtra("sms")?.let { data ->
// You should find your otp code here in `data`
}
}
}
registerReceiver(otpReceiver, filter)
Note: Once we complete our classes, you should be setting your otp value to your view. This part is left out in the code snippet as view bindings and data bindings may vary on projects.
6 —Finally, where we get to read the messages and find our code.
Code:
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.huawei.hms.common.api.CommonStatusCodes
import com.huawei.hms.support.api.client.Status
import com.huawei.hms.support.sms.common.ReadSmsConstant
class SmsBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val bundle = intent!!.extras
if (bundle != null) {
val status: Status? = bundle.getParcelable(ReadSmsConstant.EXTRA_STATUS)
if (status?.statusCode == CommonStatusCodes.TIMEOUT) {
// Process system timeout
} else if (status?.statusCode == CommonStatusCodes.SUCCESS) {
if (bundle.containsKey(ReadSmsConstant.EXTRA_SMS_MESSAGE)) {
bundle.getString(ReadSmsConstant.EXTRA_SMS_MESSAGE)?.let {
val local = Intent()
local.action = "service.to.activity.transfer"
local.putExtra("sms", it)
context!!.sendBroadcast(local)
}
}
}
}
}
}
So that’s it. You should be able to successfully register for SMS sending and retrieving, then read OTP from content and use in whatever way you like.
Thanks for reading through the guide and I hope it is simple and useful for you. Let me know if you have any suggestions or problems.
References
https://developer.huawei.com/consum...upport-sms-readsmsmanager-0000001050050553-V5
https://developer.huawei.com/consum...Guides/authotize-to-read-sms-0000001061481826
https://medium.com/huawei-developers/android-integrating-your-apps-with-huawei-hms-core-1f1e2a090e98
Click to expand...
Click to collapse
does it require any charges?
ProManojKumar said:
does it require any charges?
Click to expand...
Click to collapse
Huawei provides 30.000 sms free usage for your projects. You can control your usage under Project Quota tab in AppGallery Connect.
ProManojKumar said:
does it require any charges?
Click to expand...
Click to collapse
Huawei provides 30.000 sms free usage for your projects. You can control your usage under Project Quota tab in AppGallery Connect.
Like
Wow
Awesome
Great and nice
Like
Very interesting feature.
Awesome
Thanks
Introduction
Converting audio into text has a wide range of applications: generating video subtitles, taking meeting minutes, and writing interview transcripts. HUAWEI ML Kit's service makes doing so easier than ever before, converting audio files into meticulously accurate text, with correct punctuation as well!
Actual Effects
Build and run an app with audio file transcription integrated. Then, select a local audio file and convert it into text.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Development Preparations
For details about configuring the Huawei Maven repository and integrating the audio file transcription SDK, please refer to the Development Guide of ML Kit on HUAWEI Developers.
Declaring Permissions in the AndroidManifest.xml File
Open the AndroidManifest.xml in the main folder. Add the network connection, network status access, and storage read permissions before <application.
Please note that these permissions need to be dynamically applied for. Otherwise, Permission Denied will be reported.
Code:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Development Procedure
Creating and Initializing an Audio File Transcription Engine
Code:
Override onCreate in MainActivity to create an audio transcription engine.
private MLRemoteAftEngine mAnalyzer;
mAnalyzer = MLRemoteAftEngine.getInstance();
mAnalyzer.init(getApplicationContext());
mAnalyzer.setAftListener(mAsrListener);
Use MLRemoteAftSetting to configure the engine. The service currently supports Mandarin Chinese and English, that is, the options of mLanguage are zh and en.
Code:
MLRemoteAftSetting setting = new MLRemoteAftSetting.Factory()
.setLanguageCode(mLanguage)
.enablePunctuation(true)
.enableWordTimeOffset(true)
.enableSentenceTimeOffset(true)
.create();
enablePunctuation indicates whether to automatically punctuate the converted text, with a default value of false.
If this parameter is set to true, the converted text is automatically punctuated; false otherwise.
enableWordTimeOffset indicates whether to generate the text transcription result of each audio segment with the corresponding offset. The default value is false. You need to set this parameter only when the audio duration is less than 1 minute.
If this parameter is set to true, the offset information is returned along with the text transcription result. This applies to the transcription of short audio files with a duration of 1 minute or shorter.
If this parameter is set to false, only the text transcription result of the audio file will be returned.
enableSentenceTimeOffset indicates whether to output the offset of each sentence in the audio file. The default value is false.
If this parameter is set to true, the offset information is returned along with the text transcription result.
If this parameter is set to false, only the text transcription result of the audio file will be returned.
Creating a Listener Callback to Process the Transcription Result
private MLRemoteAftListener mAsrListener = new MLRemoteAftListener()
After the listener is initialized, call startTask in AftListener to start the transcription.
Code:
@Override
public void onInitComplete(String taskId, Object ext) {
Log.i(TAG, "MLRemoteAftListener onInitComplete" + taskId);
mAnalyzer.startTask(taskId);
}
Override onUploadProgress, onEvent, and onResult in MLRemoteAftListener.
@Override
public void onUploadProgress(String taskId, double progress, Object ext) {
Log.i(TAG, " MLRemoteAftListener onUploadProgress is " + taskId + " " + progress);
}
@Override
public void onEvent(String taskId, int eventId, Object ext) {
Log.e(TAG, "MLAsrCallBack onEvent" + eventId);
if (MLAftEvents.UPLOADED_EVENT == eventId) { // The file is uploaded successfully.
showConvertingDialog();
startQueryResult(); // Obtain the transcription result.
}
}
@Override
public void onResult(String taskId, MLRemoteAftResult result, Object ext) {
Log.i(TAG, "onResult get " + taskId);
if (result != null) {
Log.i(TAG, "onResult isComplete " + result.isComplete());
if (!result.isComplete()) {
return;
}
if (null != mTimerTask) {
mTimerTask.cancel();
}
if (result.getText() != null) {
Log.e(TAG, result.getText());
dismissTransferringDialog();
showCovertResult(result.getText());
}
List<MLRemoteAftResult.Segment> segmentList = result.getSegments();
if (segmentList != null && segmentList.size() != 0) {
for (MLRemoteAftResult.Segment segment : segmentList) {
Log.e(TAG, "MLAsrCallBack segment text is : " + segment.getText() + ", startTime is : " + segment.getStartTime() + ". endTime is : " + segment.getEndTime());
}
}
List<MLRemoteAftResult.Segment> words = result.getWords();
if (words != null && words.size() != 0) {
for (MLRemoteAftResult.Segment word : words) {
Log.e(TAG, "MLAsrCallBack word text is : " + word.getText() + ", startTime is : " + word.getStartTime() + ". endTime is : " + word.getEndTime());
}
}
List<MLRemoteAftResult.Segment> sentences = result.getSentences();
if (sentences != null && sentences.size() != 0) {
for (MLRemoteAftResult.Segment sentence : sentences) {
Log.e(TAG, "MLAsrCallBack sentence text is : " + sentence.getText() + ", startTime is : " + sentence.getStartTime() + ". endTime is : " + sentence.getEndTime());
}
}
}
}
Processing the Transcription Result in Polling Mode
After the transcription is completed, call getLongAftResult to obtain the transcription result. Process the obtained result every 10 seconds.
Code:
private void startQueryResult() {
Timer mTimer = new Timer();
mTimerTask = new TimerTask() {
@Override
public void run() {
getResult();
}
};
mTimer.schedule(mTimerTask, 5000, 10000); // Process the obtained long speech transcription result every 10s.
}
private void getResult() {
Log.e(TAG, "getResult");
mAnalyzer.setAftListener(mAsrListener);
mAnalyzer.getLongAftResult(mLongTaskId);
}
References:
To learn more, please visit:
HUAWEI Developers official website
Development Guide
Reddit to join developer discussions
GitHub or Gitee to download the demo and sample code
Stack Overflow to solve integration problems
Follow our official account for the latest HMS Core-related news and updates.
Original Source
IntroductionConverting audio into text has a wide range of applications: generating video subtitles, taking meeting minutes, and writing interview transcripts. Machine learning makes doing so easier than ever before, converting audio files into meticulously accurate text, with correct punctuation as well!
Actual EffectsBuild and run an app with audio file transcription integrated. Then, select a local audio file and convert it into text.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Development PreparationsFor details about configuring the Huawei Maven repository and integrating the audio file transcription SDK, please refer to the Development Guide of ML Kit on HUAWEI Developers.
Declaring Permissions in the AndroidManifest.xml File
Open the AndroidManifest.xml in the main folder. Add the network connection, network status access, and storage read permissions before <application.
Please note that these permissions need to be dynamically applied for. Otherwise, Permission Denied will be reported.
Code:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Development ProcedureCreating and Initializing an Audio File Transcription Engine
Override onCreate in MainActivity to create an audio transcription engine.
Code:
private MLRemoteAftEngine mAnalyzer;
mAnalyzer = MLRemoteAftEngine.getInstance();
mAnalyzer.init(getApplicationContext());
mAnalyzer.setAftListener(mAsrListener);
Use MLRemoteAftSetting to configure the engine. The service currently supports Mandarin Chinese and English, that is, the options of mLanguage are zh and en.
Code:
MLRemoteAftSetting setting = new MLRemoteAftSetting.Factory()
.setLanguageCode(mLanguage)
.enablePunctuation(true)
.enableWordTimeOffset(true)
.enableSentenceTimeOffset(true)
.create();
enablePunctuation indicates whether to automatically punctuate the converted text, with a default value of false.
If this parameter is set to true, the converted text is automatically punctuated; false otherwise.
enableWordTimeOffset indicates whether to generate the text transcription result of each audio segment with the corresponding offset. The default value is false. You need to set this parameter only when the audio duration is less than 1 minute.
If this parameter is set to true, the offset information is returned along with the text transcription result. This applies to the transcription of short audio files with a duration of 1 minute or shorter. If this parameter is set to false, only the text transcription result of the audio file will be returned.
enableSentenceTimeOffset indicates whether to output the offset of each sentence in the audio file. The default value is false.
If this parameter is set to true, the offset information is returned along with the text transcription result. If this parameter is set to false, only the text transcription result of the audio file will be returned.
Creating a Listener Callback to Process the Transcription Result
Code:
private MLRemoteAftListener mAsrListener = new MLRemoteAftListener()
After the listener is initialized, call startTask in AftListener to start the transcription.
Code:
@Override
public void onInitComplete(String taskId, Object ext) {
Log.i(TAG, "MLRemoteAftListener onInitComplete" + taskId);
mAnalyzer.startTask(taskId);
Override onUploadProgress, onEvent, and onResult in MLRemoteAftListener.
Code:
@Override
public void onUploadProgress(String taskId, double progress, Object ext) {
Log.i(TAG, " MLRemoteAftListener onUploadProgress is " + taskId + " " + progress);
}
@Override
public void onEvent(String taskId, int eventId, Object ext) {
Log.e(TAG, "MLAsrCallBack onEvent" + eventId);
if (MLAftEvents.UPLOADED_EVENT == eventId) { // The file is uploaded successfully.
showConvertingDialog();
startQueryResult(); // Obtain the transcription result.
}
}
@Override
public void onResult(String taskId, MLRemoteAftResult result, Object ext) {
Log.i(TAG, "onResult get " + taskId);
if (result != null) {
Log.i(TAG, "onResult isComplete " + result.isComplete());
if (!result.isComplete()) {
return;
}
if (null != mTimerTask) {
mTimerTask.cancel();
}
if (result.getText() != null) {
Log.e(TAG, result.getText());
dismissTransferringDialog();
showCovertResult(result.getText());
}
List<MLRemoteAftResult.Segment> segmentList = result.getSegments();
if (segmentList != null && segmentList.size() != 0) {
for (MLRemoteAftResult.Segment segment : segmentList) {
Log.e(TAG, "MLAsrCallBack segment text is : " + segment.getText() + ", startTime is : " + segment.getStartTime() + ". endTime is : " + segment.getEndTime());
}
}
List<MLRemoteAftResult.Segment> words = result.getWords();
if (words != null && words.size() != 0) {
for (MLRemoteAftResult.Segment word : words) {
Log.e(TAG, "MLAsrCallBack word text is : " + word.getText() + ", startTime is : " + word.getStartTime() + ". endTime is : " + word.getEndTime());
}
}
List<MLRemoteAftResult.Segment> sentences = result.getSentences();
if (sentences != null && sentences.size() != 0) {
for (MLRemoteAftResult.Segment sentence : sentences) {
Log.e(TAG, "MLAsrCallBack sentence text is : " + sentence.getText() + ", startTime is : " + sentence.getStartTime() + ". endTime is : " + sentence.getEndTime());
}
}
}
}
Processing the Transcription Result in Polling Mode
After the transcription is completed, call getLongAftResult to obtain the transcription result. Process the obtained result every 10 seconds.
Code:
private void startQueryResult() {
Timer mTimer = new Timer();
mTimerTask = new TimerTask() {
@Override
public void run() {
getResult();
}
};
mTimer.schedule(mTimerTask, 5000, 10000); // Process the obtained long speech transcription result every 10s.
}
private void getResult() {
Log.e(TAG, "getResult");
mAnalyzer.setAftListener(mAsrListener);
mAnalyzer.getLongAftResult(mLongTaskId);
}
References:For more details, you can go to:
ML Kit official website
ML Kit Development Documentation page, to find the documents you need
Reddit to join our developer discussion
GitHub to download ML Kit sample codes
Stack Overflow to solve any integration problems
Thanks for sharing!
Push messaging, with the proliferation of mobile Internet, has become a very effective way for mobile apps to achieve business success. It improves user engagement and stickiness by allowing developers to send messages to a wide range of users in a wide range of scenarios: taking the subway or bus, having a meal in a restaurant, having a chat... you name it. No matter what the scenario is, a push message is always a great helper for you to directly "talk" to your users, and for your users to know something informative.
Such great benefits brought by push messages, however, can be dampened by a challenge: the variety of mobile phone manufacturers. This is because usually each manufacturer has their own push messaging channels, which increases the difficulty for uniformly sending your app's push messages to mobile phones of different manufacturers. Of course there is an easy solution for this: sending your push messages to mobile phones of only one manufacturer, but this can limit your user base and prevent you from obtaining your desired messaging effects.
Then this well explains why we developers usually need to find a solution for our apps to be able to push their messages to devices of different brands.
I don't know about you, but the solution I found for my app is HMS Core Push Kit. Going on, I will demonstrate how I have integrated this kit and used its ability to aggregate third-party push messaging channels to implement push messaging on mobile phones made by different manufacturers, expecting greater user engagement and stickiness. Let's move on to the implementation.
PreparationsBefore integrating the SDK, make the following preparations:
1. Sign in to the push messaging platform of a specific manufacturer, create a project and app on the platform, and save the JSON key file of the project. (The requirements may vary depending on the manufacturer, so refer to the specific manufacturer's documentation to learn about their requirements.)
2. Configure app information in AppGallery Connet, but use the following build dependency instead when configuring the build dependencies:
Code:
dependencies {
implementation 'com.huawei.hms:push-fcm:6.3.0.304'
}
3. On the platform mentioned in the previous step, click My projects, find the app in the project, and go to Grow > Push Kit > Settings. On the page displayed, click Enable next to Configure other Android-based push, and then copy the key in the saved JSON key file and paste it in the Authentication parameters text box.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Development ProcedureNow, let's go through the development procedure.
1. Disable the automatic initialization of the SDK.
To do so, open the AndroidManifest.xml file, and add the <meta-data> element to the <application> element. Note that in the element, the name parameter has a fixed value of push_kit_auto_init_enabled. As for the value parameter, you can set it to false, indicating that the automatic initialization is disabled.
Code:
<manifest ...>
...
<application ...>
<meta-data
android:name="push_kit_auto_init_enabled"
android:value="false"/>
...
</application>
...
</manifest>
2. Initialize the push capability in either of the following ways:
Set value corresponding to push_kit_proxy_init_enabled in the <meta-data> element to true.
Code:
<application>
<meta-data
android:name="push_kit_proxy_init_enabled"
android:value="true" />
</application>
Explicitly call FcmPushProxy.init in the onCreate method of the Application class.
3. Call the getToken method to apply for a token.
Code:
private void getToken() {
// Create a thread.
new Thread() {
@Override
public void run() {
try {
// Obtain the app ID from the agconnect-services.json file.
String appId = "your APP_ID";
// Set tokenScope to HCM.
String tokenScope = "HCM";
String token = HmsInstanceId.getInstance(MainActivity.this).getToken(appId, tokenScope);
Log.i(TAG, "get token: " + token);
// Check whether the token is empty.
if(!TextUtils.isEmpty(token)) {
sendRegTokenToServer(token);
}
} catch (ApiException e) {
Log.e(TAG, "get token failed, " + e);
}
}
}.start();
}
private void sendRegTokenToServer(String token) {
Log.i(TAG, "sending token to server. token:" + token);
}
4. Override the onNewToken method.
After the SDK is integrated and initialized, the getToken method will not return a token. Instead, you'll need to obtain a token by using the onNewToken method.
Code:
@Override
public void onNewToken(String token, Bundle bundle) {
Log.i(TAG, "onSubjectToken called, token:" + token );
}
5. Override the onTokenError method.
This method will be called if the token fails to be obtained.
Code:
@Override
public void onTokenError(Exception e, Bundle bundle) {
int errCode = ((BaseException) e).getErrorCode();
String errInfo = e.getMessage();
Log.i(TAG, "onTokenError called, errCode:" + errCode + ",errInfo=" + errInfo );
}
6. Override the onMessageReceived method to receive data messages.
Code:
@Override
public void onMessageReceived(RemoteMessage message) {
Log.i(TAG, "onMessageReceived is called");
// Check whether the message is empty.
if (message == null) {
Log.e(TAG, "Received message entity is null!");
return;
}
// Obtain the message content.
Log.i(TAG, "get Data: " + message.getData()
+ "\n getFrom: " + message.getFrom()
+ "\n getTo: " + message.getTo()
+ "\n getMessageId: " + message.getMessageId()
+ "\n getSentTime: " + message.getSentTime()
+ "\n getDataMap: " + message.getDataOfMap()
+ "\n getMessageType: " + message.getMessageType()
+ "\n getTtl: " + message.getTtl()
+ "\n getToken: " + message.getToken());
Boolean judgeWhetherIn10s = false;
// Create a job to process a message if the message is not processed within 10 seconds.
if (judgeWhetherIn10s) {
startWorkManagerJob(message);
} else {
// Process the message within 10 seconds.
processWithin10s(message);
}
}
private void startWorkManagerJob(RemoteMessage message) {
Log.d(TAG, "Start new job processing.");
}
private void processWithin10s(RemoteMessage message) {
Log.d(TAG, "Processing now.");
}
7. Send downlink messages.
Currently, you can only use REST APIs on the server to send downlink messages through a third-party manufacturer's push messaging channel.
The following is the URL for calling the API using HTTPS POST:
Code:
POST https://push-api.cloud.huawei.com/v1/[appId]/messages:send
The request header looks like the following:
Code:
Content-Type: application/json; charset=UTF-8
Authorization: Bearer CF3Xl2XV6jMKZgqYSZFws9IPlgDvxqOfFSmrlmtkTRupbU2VklvhX9kC9JCnKVSDX2VrDgAPuzvNm3WccUIaDg==
An example of the notification message body is as follows:
Code:
{
"validate_only": false,
"message": {
"android": {
"notification": {
"title": "test title",
"body": "test body",
"click_action": {
"type": 3
}
}
},
"token": ["pushtoken1"]
}
}
And just like that, my app has got the ability to send its push messages to mobile phones of different manufacturers — without any other configurations. Easy-peasy, right?
ConclusionToday's highly developed mobile Internet has made push messaging an important and effective way for mobile apps to improve user engagement and stickiness. A great obstacle for push messaging to effectively play its role is the highly diversified mobile phone market that is inundated with various manufacturers.
In this article, I demonstrated my solution to aggregate the push channels of different manufacturers, which allowed my app to push messages in a unified way to devices made by those manufacturers. As proven, the whole implementation process is both straightforward and cost-effective, delivering a better messaging effect of push messages by ensuring that they can reach a bigger user base supported by various manufacturers.