Integrating Text to speech (TTS) in B4A Platform - Huawei Developers

{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Introduction
This article talks about how to convert text to Text to speech (TTS) can convert text information into audio output. Rich timbres are provided and the volume and speed can be adjusted, thereby natural voices can be produced. This service uses the deep neural network (DNN) synthesis mode and can be quickly integrated through the on-device SDK to generate audio data in real time. In the current version, two standard male voices and six standard female voices are available.
TTS is available only on Huawei phones.
The text in a single request can contain a maximum of 500 characters and is encoded using UTF-8.
TTS in French, Spanish, German, and Italian is deployed only in Asia, Africa, Latin America, and Europe.
TTS depends on on-cloud APIs. During commissioning and usage, ensure that the device can access the Internet.
Default specifications of the real-time output audio data are as follows: PCM mono, 16-bit depth, and 16 kHz audio sampling rate.
Follow all the steps mentioned in Basic Setup to start HMS integration on B4A Platform.
Refer to previous Article
Enable ML Kit in App gallery connect.
Tips & Tricks:
In case voice is not audible please make ensure that ML-Kit is enabled or not in App Gallery Connect.
Creating Wrapper Class
1. Downloading the AAR Packages and JSON File
Sign in to HUAWEI Developer and download the AAR packages required.
AAR packages related to HMS TTS kit is displayed as below
Agconnect-core:
https://developer.huawei.com/repo/com/huawei/agconnect/agconnect-core/1.0.0.300/agconnect-core-1.0.0.300.aar
HMSSDKBase:
https://developer.huawei.com/repo/com/huawei/hms/base/4.0.1.300/base-4.0.1.300.aar
Ml-computer-agc-inner:
https://developer.huawei.com/repo/com/huawei/hms/ml-computer-agc-inner/2.0.1.300/ml-computer-agc-inner-2.0.1.300.aar
Ml-computer-commonutils-inner:
https://developer.huawei.com/repo/com/huawei/hms/ml-computer-commonutils-inner/2.0.1.300/ml-computer-commonutils-inner-2.0.1.300.aar
Ml-computer-ha-inner:
https://developer.huawei.com/repo/com/huawei/hms/ml-computer-ha-inner/2.0.0.300/ml-computer-ha-inner-2.0.0.300.aar
Tasks:
https://developer.huawei.com/repo/com/huawei/hmf/tasks/1.4.1.300/tasks-1.4.1.300.aar
Network-common:
https://developer.huawei.com/repo/com/huawei/hms/network-common/4.0.2.301/network-common-4.0.2.301.aar
Network-grs:
https://developer.huawei.com/repo/com/huawei/hms/network-grs/4.0.2.301/network-grs-4.0.2.301.aar
Ml-computer-grs-inner:
https://developer.huawei.com/repo/com/huawei/hms/ml-computer-grs-inner/2.0.0.300/ml-computer-grs-inner-2.0.0.300.aar
Ml-computer-net:
https://developer.huawei.com/repo/com/huawei/hms/ml-computer-net/1.0.4.300/ml-computer-net-1.0.4.300.aar
Okhttp:
https://jar-download.com/artifacts/com.squareup.okhttp3/okhttp/3.12.0/source-code
Okio:
https://mvnrepository.com/artifact/com.squareup.okio/okio/1.15.0
Ml-computer-voice-tts:
https://developer.huawei.com/repo/com/huawei/hms/ml-computer-voice-tts/2.0.0.401/ml-computer-voice-tts-2.0.0.401.aar
2. Open AAR packages with rar tool and rename the class.jar and AndroidManifest.xml files. And save those jar file inside libs folder (It is recommended that the two files be renamed consistently with the AAR package names.)
3. Copy required permissions in the <manifest> section in B4A IDE
4. Copy all configurations in the <application> section
5. Change ${applicationId} to $PACKAGE$.
6. Download the configuration file (agconnect-services.json) from App Gallery Connect and Add the JSON File to the assets Folder of the AAR file as shown below.
[/CENTER]
B4A will automatically incorporate files in the assets folder of an AAR package to the assets folder of your main project.
Encapsulating Java Files Using SLC
1. Create Library as parent and then create bin, libs and src as subfolder in the project directory.
2. Develop java project inside the following path, Choose Library Folder > src > b4x > tts > demo
3. Import the following two lines of code to each Java file
Code:
import anywheresoftware.b4a.BA;
import anywheresoftware.b4a.BA.*;
import anywheresoftware.b4a.IOnActivityResult;
4. Add necessary annotations to the Java files
Code:
@DependsOn(values={
"base-4.0.1.300.aar",
"agconnect-core-1.0.1.300.aar",
"tasks-1.3.1.302.aar",
"network-common-4.0.2.301.aar",
"network-grs-4.0.2.301.aar",
"ml-computer-net-2.0.1.300.aar",
"ml-computer-grs-inner-2.0.1.300.aar",
"okhttp-3.12.0.jar",
"okio-1.15.0.jar",
"ml-computer-voice-tts-2.0.1.300.aar",
"ml-computer-agc-inner-2.0.1.300.aar",
"ml-computer-commonutils-inner-2.0.1.300.aar",
"ml-computer-ha-inner-2.0.1.300.aar"
})
5. Modify the context.
B4A does not recognize the Context class. Therefore, when parsing a class that contains the @vercion(1.0f) annotation, it will report an error if a method of the class has referenced Context. In this case, you need to change Context to the B4A object BA
Code:
@ShortName("ttsWork")
@Events(values = {"TTSStop (token As String)"})
public class ttsWork {
public static String eventName = "listener";
public static final String TAG = "tts_Kit";
public static void init(Context context) {
AGConnectServicesConfig.fromContext(context).overlayWith(new LazyInputStream(context) {
@Override
public InputStream get(Context context) {
try {
return context.getAssets().open("agconnect-services.json");
} catch (IOException e) {
e.printStackTrace();
BA.Log(e.toString());
}
return null;
}
});
}
private static IOnActivityResult ion;
public static void ListenFortts(final Context context, String text) {
MLApplication.getInstance().setApiKey("API_KEY");
MLTtsConfig mlConfigs = new MLTtsConfig().setLanguage(MLTtsConstants.TTS_EN_US).setPerson(MLTtsConstants.TTS_SPEAKER_FEMALE_EN).setSpeed(1.0f).setVolume(1.0f);
MLTtsEngine mlTtsEngine = new MLTtsEngine(mlConfigs);
Log.d(" text from ui ", text);
mlTtsEngine.updateConfig(mlConfigs);
mlTtsEngine.setTtsCallback(callback);
String sourceText = "Welcome";
String id = mlTtsEngine.speak(text, MLTtsEngine.QUEUE_APPEND);
}
static MLTtsCallback callback = new MLTtsCallback() {
@Override
public void onError(String taskId, MLTtsError err) {
}
@Override
public void onWarn(String taskId, MLTtsWarn warn) {
}
@Override
public void onRangeStart(String taskId, int start, int end) {
}
@Override
public void onAudioAvailable(String taskId, MLTtsAudioFragment audioFragment, int offset, Pair<Integer, Integer> range, Bundle bundle)
{
}
@Override
public void onEvent(String taskId, int eventId, Bundle bundle) {
// Log.v(" stop service ","text1111");
switch (eventId) {
case MLTtsConstants.EVENT_PLAY_START:
Log.v(" stop service ", "textplaystart");
ba.raiseEventFromUI(this, eventName + "_TTSText", "text");
Log.v(" stop service ", "text11");
break;
case MLTtsConstants.EVENT_PLAY_STOP:
boolean isInterrupted = bundle.getBoolean(MLTtsConstants.EVENT_PLAY_STOP_INTERRUPTED);
Log.v(" stop service ", "textplaystop");
ba.raiseEventFromUI(this, eventName + "_TTSStop", "Service Stopped");
Log.v(" stop service ", "text11");
break;
case MLTtsConstants.EVENT_PLAY_RESUME:
break;
case MLTtsConstants.EVENT_PLAY_PAUSE:
break;
case MLTtsConstants.EVENT_SYNTHESIS_START:
break;
case MLTtsConstants.EVENT_SYNTHESIS_END:
break;
case MLTtsConstants.EVENT_SYNTHESIS_COMPLETE:
boolean isInterrupted1 = bundle.getBoolean(MLTtsConstants.EVENT_SYNTHESIS_INTERRUPTED);
break;
default:
break;
}
}
};
}

Can we customize languages ?

Related

Product Visual Search – Ultimate Guide

More information like this, you can visit HUAWEI Developer Forum​
Introduction
HMS ML kit service searches in pre-established product image library for the same or similar products based on a product image taken by a customer, and returns the IDs of those products and related information.
{
"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"
}
Use Case
We will capture the product image using device camera from our developed shopping application.
We will show the returned products list in recycle view.
Prerequisite
Java JDK 1.8 or higher is recommended.
Android Studio is recommended.
Huawei android device with HMS core 4.0.0.300 or higher.
Before developing an app, you will need to register as a HUAWEI developer. Refer to Register a HUAWEI ID.
Integrating app gallery connect SDK. Refer to AppGallery Connect Service Getting Started.
Implementation
Enable ML kit in Manage APIs. Refer to Service Enabling.
Integrate following dependencies in app-level build.gradle.
Code:
// Import the product visual search SDK.
implementation 'com.huawei.hms:ml-computer-vision-cloud:2.0.1.300'
3. Add agc plugin in the top of app.gradle file.
Code:
apply plugin: 'com.huawei.agconnect'
4. Add the following permission in manifest.
Camera permission android.permission.CAMERA: Obtains real-time images or videos from a camera.
Internet access permission android.permission.INTERNET: Accesses cloud services on the Internet.
Storage write permission android.permission.WRITE_EXTERNAL_STORAGE: Upgrades the algorithm version.
Storage read permission android.permission.READ_EXTERNAL_STORAGE: Reads photos stored on a device.
5. To request camera permission in realtime.
Code:
private void requestCameraPermission() {
final String[] permissions = new String[] {Manifest.permission.CAMERA};
if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
ActivityCompat.requestPermissions(this, permissions, this.CAMERA_PERMISSION_CODE);
return;
}
}
6. Add following code in Application class
Code:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
MLApplication.getInstance().setApiKey("API KEY");
}
}
API key can be obtained either from AGC or integrated agcconnect-services.json.
7. To create an analyzer for product visual search.
Code:
private void initializeProductVisionSearch() {
MLRemoteProductVisionSearchAnalyzerSetting settings = new MLRemoteProductVisionSearchAnalyzerSetting.Factory()
// Set the maximum number of products that can be returned.
.setLargestNumOfReturns(16)
// Set the product set ID. (Contact [email protected] to obtain the configuration guide.)
// .setProductSetId(productSetId)
// Set the region.
.setRegion(MLRemoteProductVisionSearchAnalyzerSetting.REGION_DR_CHINA)
.create();
analyzer
= MLAnalyzerFactory.getInstance().getRemoteProductVisionSearchAnalyzer(settings);
}
8. To capture image from camera.
Code:
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, REQ_CAMERA_CODE);
9. Once image has been captured, onActivityResult() method will be executed.
Code:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.d(TAG, "onActivityResult");
if(requestCode == 101) {
if (resultCode == RESULT_OK) {
Bitmap bitmap = (Bitmap) data.getExtras().get("data");
if (bitmap != null) {
// Create an MLFrame object using the bitmap, which is the image data in bitmap format.
MLFrame mlFrame = new MLFrame.Creator().setBitmap(bitmap).create();
mlImageDetection(mlFrame);
}
}
}
}
private void mlImageDetection(MLFrame mlFrame) {
Task> task = analyzer.asyncAnalyseFrame(mlFrame);
task.addOnSuccessListener(new OnSuccessListener>() {
public void onSuccess(List products) {
// Processing logic for detection success.
displaySuccess(products);
}})
.addOnFailureListener(new OnFailureListener() {
public void onFailure(Exception e) {
// Processing logic for detection failure.
// Recognition failure.
try {
MLException mlException = (MLException)e;
// Obtain the result code. You can process the result code and customize respective messages displayed to users.
int errorCode = mlException.getErrCode();
// Obtain the error information. You can quickly locate the fault based on the result code.
String errorMessage = mlException.getMessage();
} catch (Exception error) {
// Handle the conversion error.
}
}
});
}
private void displaySuccess(List productVisionSearchList) {
List productImageList = new ArrayList();
String prodcutType = "";
for (MLProductVisionSearch productVisionSearch : productVisionSearchList) {
Log.d(TAG, "type: " + productVisionSearch.getType() );
prodcutType = productVisionSearch.getType();
for (MLVisionSearchProduct product : productVisionSearch.getProductList()) {
productImageList.addAll(product.getImageList());
Log.d(TAG, "custom content: " + product.getCustomContent() );
}
}
StringBuffer buffer = new StringBuffer();
for (MLVisionSearchProductImage productImage : productImageList) {
String str = "ProductID: " + productImage.getProductId() + "
ImageID: " + productImage.getImageId() + "
Possibility: " + productImage.getPossibility();
buffer.append(str);
buffer.append("
");
}
Log.d(TAG , "display success: " + buffer.toString());
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.main_fragment_container, new SearchResultFragment(productImageList, prodcutType ));
transaction.commit();
}
onSuccess() callback will give us list of MLProductVisionSearch objects, which can be used to get product id and image URL of each product. Also we can get the product type using productVisionSearch.getType(). getType() returns number which can be mapped.
10. We can achieve the product type mapping with following code.
Code:
private String getProductType(String type) {
switch(type) {
case "0":
return "Others";
case "1":
return "Clothing";
case "2":
return "Shoes";
case "3":
return "Bags";
case "4":
return "Digital & Home appliances";
case "5":
return "Household Products";
case "6":
return "Toys";
case "7":
return "Cosmetics";
case "8":
return "Accessories";
case "9":
return "Food";
}
return "Others";
}
11. To get product id and image URL from MLVisionSearchProductImage.
Code:
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
final MLVisionSearchProductImage mlProductVisionSearch = productVisionSearchList.get(position);
holder.tvTitle.setText(mlProductVisionSearch.getProductId());
Glide.with(context)
.load(mlProductVisionSearch.getImageId())
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(holder.imageView);
}
Images
Reference
https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides-V5/sdk-data-security-0000001050040129-V5

Location Kit Integration in Unity - Official Plugin (Huawei HMS Core App Services)

More information like this, you can visit HUAWEI Developer Forum​
Original link: https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0201350575696000176&fid=0101187876626530001
Introduction:
In this article, we will cover Integration of Location Kit in Unity Project using Official Plugin (Huawei HMS Core App Services).
{
"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"
}
Requirements:
1. Unity Editor
2. Huawei device
3. Visual Studio
Output:
Fetch and show the users current location (Latitude and Longitude) using Huawei Location Kit.
Follows the steps.
1. Create Unity Project.
Click unity icon.
Click NEW, select 3D, Project Name and Location.
Click CREATE, as follows.
2. Click Asset Store, search Huawei HMS Core App Services and click Import, as follows.
3. Once import is successful, verify directory in Assets > Huawei HMS Core App Services path, as follows.
4. Click Console and create a New Project.
5. Choose Project Settings > Player and edit the required options in Publishing Settings, as follows.
6. Verify the files created in Step 5.
7. Download agconnect-services.json and copy to Assets/Plugins/Android, as follows.
8. Update the Package Name.
9. Open LauncherTemplate.gradle and add below line
Code:
implementation 'com.android.support:appcompat-v7:28.0.0'
10. Open AndroidManifest file & Add below permissions.
Code:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
11. Add Receiver Like shown below in Android Manifest File.
Code:
<receiver
android:name="com.unity.hms.location.LocationBroadcastReceiver"
android:exported="true">
</receiver>
12. Open "baseProjectTemplate.gradle" and add lines, as follows.
Code:
maven {url 'https://developer.huawei.com/repo/'}
13. Open "mainTemplate.gradle" and add lines like shown below
Code:
implementation 'com.huawei.hms:location:5.0.0.301'
implementation 'com.huawei.hms:ads-lite:13.4.30.301'
14. Scripting, create two classes.
TestClass.cs
Code:
using System.Collections;
using System.Collections.Generic;
using HuaweiHms;
using UnityEngine;
public class TestClass : IBroadcastReceiver
{
override
public void onReceive(Context arg0, Intent arg1)
{
Debug.LogError("kamal onReceive--->");
}
}
RegisterReceiver.cs
Code:
using System.Collections;
using System.Collections.Generic;
using HuaweiHms;
using UnityEngine;
using UnityEngine.UI;
public class RegisterReceiver : MonoBehaviour
{
static FusedLocationProviderClient fusedLocationProviderClient;
static LocationRequest locatinoRequest;
public Text latitude;
public Text longitude;
private void Awake()
{
//AdListener add = new AdListener();
TestClass receiver = new TestClass();
BroadcastRegister.CreateLocationReceiver(receiver);
Debug.LogError("kamal RegisterReceiver--->");
//LocationRequest request = new LocationRequest();
//request.setInterval(10000);
//request.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
locatinoRequest = LocationRequest.create();
locatinoRequest.setInterval(10000);
locatinoRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
builder.addLocationRequest(locatinoRequest);
LocationSettingsRequest locationSettingsRequest = builder.build();
Activity act = new Activity();
//Context context = new Context();
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(act);
SettingsClient settingsClient = LocationServices.getSettingsClient(act);
settingsClient.checkLocationSettings(locationSettingsRequest)
.addOnSuccessListener(new OnSuccessListenerTemp(this))
.addOnFailureListener(new OnFailureListenerTemp());
Debug.LogError("kamal RegisterReceiver request send--->");
}
class OnSuccessListenerTemp : OnSuccessListener
{
private RegisterReceiver registerReceiver;
public OnSuccessListenerTemp(RegisterReceiver registerReceiver)
{
this.registerReceiver = registerReceiver;
}
public override void onSuccess(AndroidJavaObject arg0) {
Debug.LogError("kamal onSuccess 0--->");
fusedLocationProviderClient.requestLocationUpdates(locatinoRequest, new OnLocationCallback(this.registerReceiver), Looper.getMainLooper())
.addOnSuccessListener(new OnReqSuccessListenerTemp())
.addOnFailureListener(new OnReqFailureListenerTemp())
;
}
};
class OnReqSuccessListenerTemp : OnSuccessListener
{
public override void onSuccess(AndroidJavaObject arg0)
{
Debug.LogError("kamal onSuccess 1--->");
}
};
class OnReqFailureListenerTemp : OnFailureListener
{
public override void onFailure(Exception arg0)
{
Debug.LogError("kamal onFailure 2--->");
}
}
class OnLocationCallback : LocationCallback {
private RegisterReceiver registerReceiver;
public OnLocationCallback(RegisterReceiver registerReceiver)
{
this.registerReceiver = registerReceiver;
}
public override void onLocationAvailability(LocationAvailability arg0) {
Debug.LogError("kamal onLocationAvailability 0--->");
}
public override void onLocationResult(LocationResult locationResult) {
Location location = locationResult.getLastLocation();
HWLocation hWLocation = locationResult.getLastHWLocation();
Debug.LogError("kamal onLocationResult found location--->");
if (location != null) {
Debug.LogError("kamal getLatitude--->" + location.getLatitude() + "<-getLongitude->" + location.getLongitude());
//latitude.text = "Latitude-->" + location.getLatitude();
//longitude.text = "Longitude-->" + location.getLongitude() ;
//RegisterReceiver.this.updateData(location);
registerReceiver.updateData(location);
}
if (hWLocation != null)
{
string country = hWLocation.getCountryName();
string city = hWLocation.getCity();
string countryCode = hWLocation.getCountryCode();
string dd = hWLocation.getPostalCode();
Debug.LogError("kamal country--->"+country + "<-city->"+city+ "<-countrycode->"+countryCode+"<-postal code->"+dd);
}
else {
Debug.LogError("kamal onLocationResult found location hWLocation is null--->");
}
}
}
private void updateData(Location location) {
latitude.text = "Latitude-->" + location.getLatitude();
longitude.text = "Longitude-->" + location.getLongitude() ;
}
class OnFailureListenerTemp : OnFailureListener {
public override void onFailure(Exception arg0) {
Debug.LogError("kamal onFailure--->");
}
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
15. Code explanation, follow below URL.
https://developer.huawei.com/consumer/en/codelab/HMSLocationKit/index.html#5
16. Run Attach Script to Scene, create two Text Views and assign them to script for showing latitude and longitude.
17. Run the project and enable location permission manually, as its demo I did not do it programmatically.
Conclusion
Latitude and Longitude both are coming on game screen, as shown below
References
Integrate Unity & Account Kit
https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0202334275608690050&fid=0101187876626530001
Integrate Unity & In App purchase
https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0201335993101470045&fid=0101187876626530001
HMS Ads integration with Unity
https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0201337158850280060&fid=0101187876626530001

Integrating In App Purchase in B4A Platform

Introduction
HUAWEI In-App Purchases (IAP) aggregates multiple payment methods for global payments, and can be easily integrated into your app to help revenue increase. Users can purchase a variety of products or services, including common virtual products and subscriptions and directly within your app. For details, please refer to HUAWEI IAP 5.0.
First you need to understand three types of in-app products supported by HUAWEI In-App Purchases.
1. Consumable: Consumables are used once, are depleted, and can be purchased again.
Example: Extra lives and gems in a game
2. Non-consumable: Non-consumables are purchased once and do not expire.
Example: Extra game levels in a game or permanent membership of an app
3. Auto-renewable subscriptions: Users can purchase access to value-added functions or content in a specified period of time. The subscriptions are automatically renewed on a recurring basis until users decide to cancel.
Example: Non-permanent membership of an app, such as a monthly video membership
For details, refer to
https://www.b4x.com/b4a.html
Follow all the steps mentioned in Basic Setup to start HMS integration on B4A Platform.
Refer to
https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0201286424114350051&fid=0101246461018590361
Enable In-App Purchases in App gallery connect.
{
"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"
}
Tips & Tricks:
Before enable In-App Purchases create marchant account. It will take two days to approve then only we can enable In-App Purchases in AppGallery Connect.
Creating Wrapper Class
1. Downloading the AAR Packages and JSON File
Sign in to HUAWEI Developer and download the AAR packages required.
AAR packages related to HMS In-App Purchases kit, as follows.
HMSSDK Agconnect-Core:
https://developer.huawei.com/repo/com/huawei/agconnect/agconnect-core/1.4.0.300/agconnect-core-1.4.0.300.aar
HMSSDK Tasks:
https://developer.huawei.com/repo/com/huawei/hmf/tasks/1.4.1.300/tasks-1.4.1.300.aar
HMSSDK Available Update:
https://developer.huawei.com/repo/com/huawei/hms/availableupdate/5.0.0.301/availableupdate-5.0.0.301.aar
HMSSDK Base:
https://developer.huawei.com/repo/com/huawei/hms/base/5.0.0.301/base-5.0.0.301.aar
HMSSDK Device:
https://developer.huawei.com/repo/com/huawei/hms/device/5.0.0.301/device-5.0.0.301.aar
HMSSDK Log:
https://developer.huawei.com/repo/com/huawei/hms/log/5.0.0.301/log-5.0.0.301.aar
HMSSDK Network-Common:
https://developer.huawei.com/repo/com/huawei/hms/network-common/4.0.2.300/network-common-4.0.2.300.aar
HMSSDK Network-Grs:
https://developer.huawei.com/repo/com/huawei/hms/network-grs/4.0.2.300/network-grs-4.0.2.300.aar
HMSSDK Stats:
https://developer.huawei.com/repo/com/huawei/hms/stats/5.0.0.301/stats-5.0.0.301.aar
HMSSDK UI:
https://developer.huawei.com/repo/com/huawei/hms/ui/5.0.0.301/ui-5.0.0.301.aar
HMSSDK Update:
https://developer.huawei.com/repo/com/huawei/hms/update/2.0.6.302/update-2.0.6.302.aar
HMSSDK IAP:
https://developer.huawei.com/repo/com/huawei/hms/iap/5.0.2.300/iap-5.0.2.300.aar
2. Open AAR packages with rar tool and rename the class.jar and AndroidManifest.xml files. And save those jar file inside libs folder (It is recommended that the two files be renamed consistently with the AAR package names.).
3. Copy required permissions in the <manifest> section in B4A IDE.
4. Copy all the configurations in <application> section.
5. Change ${applicationId} to $PACKAGE$.
6. Download the configuration file(agconnect-services.json) from App Gallery Connect and add the JSON File to the assets Folder of the AAR file as shown below.
B4A will automatically incorporate files in the assets folder of an AAR package to the assets folder of your main project.
Setting Package Signing Options
To create a signed application package perform the following steps:
1. Choose Project > Build Configurations and change the B4A project Package name to the project package name configured in App Gallery Connect.
2. Choose Tools > Private Sign Key and select the SHA256 signature file specified in AppGallery Connect as the B4A project signature file.
2. Declare MainApplication in the Manifest file.
The path of MainApplication must also be the path to src folder where MainApplication is located.
Encapsulating Java Files Using SLC
1. Create Library as parent and then create bin, libs and src as subfolder in the project directory
2. Develop java project inside the following path:
Choose Library Folder > src > b4x > iap > demo
3. Import the following two lines of code to each Java file.
Code:
import anywheresoftware.b4a.BA;
import anywheresoftware.b4a.BA.*;
import com.huawei.hmf.tasks.Task;
import android.content.Intent;
import anywheresoftware.b4a.IOnActivityResult;
4. Add necessary annotations to the Java files.
Code:
@DependsOn(values={ "agconnect-core-1.4.0.300.aar",
"base-5.0.0.301.aar",
"network-common-4.0.2.300.aar",
"network-grs-4.0.2.300.aar",
"iap-5.0.2.300.aar",
"availableupdate-5.0.0.301.aar",
"device-5.0.0.301.aar",
"log-5.0.0.301.aar",
"stats-5.0.0.301.aar",
"tasks-1.4.1.300.aar",
"ui-5.0.0.301.aar",
"update-2.0.6.302.aar",
})
5. Modify the context.
B4A does not recognize the Context class. Therefore, when parsing a class that contains @version (1.0f) annotation, it will report an error if a method of the class has referenced Context . In this case, you need to change Context to B4A object BA.
Code:
@ShortName("IAPWork")
public class IAPWork {
public static String eventName = "listener";
public static String TAG = "IAP_Kit";
public static BA ba;
public void init(Context context) {
AGConnectServicesConfig.fromContext(context).overlayWith(new LazyInputStream(context) {
@Override
public InputStream get(Context context) {
try {
return context.getAssets().open("agconnect-services.json");
} catch (IOException e) {
e.printStackTrace();
BA.Log(e.toString());
}
return null;
}
});
}
public void loadProduct(BA ba1) {
ba = ba1;
// obtain in-app product details configured in AppGallery Connect, and then show the products
IapClient iapClient = Iap.getIapClient(ba.context);
Task<ProductInfoResult> task = iapClient.obtainProductInfo(createProductInfoReq());
task.addOnSuccessListener(new OnSuccessListener<ProductInfoResult>() {
@Override
public void onSuccess(ProductInfoResult result) {
ba.raiseEventFromUI(this, eventName + "_iaparray", result.toString());
if (result != null && !result.getProductInfoList().isEmpty()) {
List<ProductInfo> productInfoList = result.getProductInfoList();
for (ProductInfo productInfo : productInfoList) {
ba.raiseEventFromUI(this, eventName + "_iaptext", productInfo.getProductName(), productInfo.getProductId(), productInfo.getPrice());
}
//ba.raiseEventFromUI(this,eventName+ "_iaparray",new Object[] {productInfoList} );
// showProduct(result.getProductInfoList());
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.e(TAG, e.getMessage());
if (e instanceof IapApiException) {
IapApiException iapApiException = (IapApiException) e;
int returnCode = iapApiException.getStatusCode();
if (returnCode == OrderStatusCode.ORDER_HWID_NOT_LOGIN) {
ba.raiseEventFromUI(this, eventName + "_iaparray", "Please sign in to the app with a HUAWEI ID.");
} else {
ba.raiseEventFromUI(this, eventName + "_iaparray", e.getMessage());
// Toast.makeText(ba.context, e.getMessage(), Toast.LENGTH_SHORT).show();
}
} else {
ba.raiseEventFromUI(this, eventName + "_iaparray", "error");
// Toast.makeText(ba.context, "error", Toast.LENGTH_SHORT).show();
}
}
});
}
private ProductInfoReq createProductInfoReq() {
ProductInfoReq req = new ProductInfoReq();
// In-app product type contains:
// 0: consumable
// 1: non-consumable
// 2: auto-renewable subscription
req.setPriceType(IapClient.PriceType.IN_APP_CONSUMABLE);
ArrayList<String> productIds = new ArrayList<>();
// Pass in the item_productId list of products to be queried.
// The product ID is the same as that set by a developer when configuring product information in AppGallery Connect.
productIds.add("ProductID1");
productIds.add("ProductID2");
productIds.add("ProductID3");
productIds.add("ProductID4");
req.setProductIds(productIds);
ba.raiseEventFromUI(this, eventName + "_iaparray", productIds.get(0));
return req;
}
}
Tips & Tricks
The account for sandbox testing takes effect 30 minutes to 1 hour after being added. Check whether the current account can be used in the sandbox testing.

Intermediate: How to Verify Phone Number and Anonymous Account Login using Huawei Auth Service-AGC in Unity

Introduction
In this article, we will looking that how Huawei Auth Service-AGC provides secure and reliable user authentication system to your application. However building such systems is very difficult process, using Huawei Auth Service SDK only need to access Auth Service capabilities without implementing on the cloud.
Here I am covering sign in anonymously which is anonymous account sign to access your app as guest when you wish to login anonymously, Auth Service provides you unique id to uniquely identify user. And authenticating mobile number by verifying through OTP.
{
"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"
}
Overview
You need to install Unity software and I assume that you have prior
knowledge about the unity and C#.
Hardware Requirements
A computer (desktop or laptop) running Windows 10.
A Huawei phone (with the USB cable), which is used for debugging.
Software Requirements
Java JDK 1.7 or later.
Unity software installed.
Visual Studio/Code installed.
HMS Core (APK) 4.X or later.
Integration Preparations
1. Create a project in AppGallery Connect.
2. Create Unity project.
3. Huawei HMS AGC Services to project.
4. Download and save the configuration file.
Add the agconnect-services.json file following directory Assests > Plugins > Android
5. Add the following plugin and dependencies in LaucherTemplate.
Code:
apply plugin: 'com.huawei.agconnect'
6. Add the following dependencies in MainTemplate.
Code:
apply plugin: 'com.huawei.agconnect'
implementation 'com.huawei.agconnect:agconnect-auth:1.4.2.301'
implementation 'com.huawei.hms:base:5.2.0.300'
implementation 'com.huawei.hms:hwid:5.2.0.300'
7. Add dependencies in build script repositories and all project repositories & class path in BaseProjectTemplate.
Code:
maven { url 'https://developer.huawei.com/repo/' }
8. Create Empty Game object rename to GameManager, UI canvas input text fields and buttons and assign onclick events to respective components as shown below.
MainActivity.java
Code:
package com.huawei.AuthServiceDemo22;
import android.content.Intent;
import android.os.Bundle;
import com.hw.unity.Agc.Auth.ThirdPartyLogin.LoginManager;
import com.unity3d.player.UnityPlayerActivity;
import android.util.Log;
import com.huawei.agconnect.auth.AGConnectAuth;
import com.huawei.agconnect.auth.AGConnectAuthCredential;
import com.huawei.agconnect.auth.AGConnectUser;
import com.huawei.agconnect.auth.PhoneAuthProvider;
import com.huawei.agconnect.auth.SignInResult;
import com.huawei.agconnect.auth.VerifyCodeResult;
import com.huawei.agconnect.auth.VerifyCodeSettings;
import com.huawei.hmf.tasks.OnFailureListener;
import com.huawei.hmf.tasks.OnSuccessListener;
import com.huawei.hmf.tasks.Task;
import com.huawei.hmf.tasks.TaskExecutors;
import java.util.Locale;
public class MainActivity extends UnityPlayerActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LoginManager.getInstance().initialize(this);
Log.d("DDDD"," Inside onCreate ");
}
public static void AnoniomousLogin(){
AGConnectAuth.getInstance().signInAnonymously().addOnSuccessListener(new OnSuccessListener<SignInResult>() {
@Override
public void onSuccess(SignInResult signInResult) {
AGConnectUser user = signInResult.getUser();
String uid = user.getUid();
Log.d("DDDD"," Login Anonymous UID : "+uid);
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.d("DDDD"," Inside ERROR "+e.getMessage());
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
LoginManager.getInstance().onActivityResult(requestCode, resultCode, data);
}
public static void sendVerifCode(String phone) {
VerifyCodeSettings settings = VerifyCodeSettings.newBuilder()
.action(VerifyCodeSettings.ACTION_REGISTER_LOGIN)
.sendInterval(30) // Shortest sending interval, 30–120s
.build();
String countCode = "+91";
String phoneNumber = phone;
if (notEmptyString(countCode) && notEmptyString(phoneNumber)) {
Task<VerifyCodeResult> task = PhoneAuthProvider.requestVerifyCode(countCode, phoneNumber, settings);
task.addOnSuccessListener(TaskExecutors.uiThread(), new OnSuccessListener<VerifyCodeResult>() {
@Override
public void onSuccess(VerifyCodeResult verifyCodeResult) {
Log.d("DDDD"," ==>"+verifyCodeResult);
}
}).addOnFailureListener(TaskExecutors.uiThread(), new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.d("DDDD"," Inside onFailure");
}
});
}
}
static boolean notEmptyString(String string) {
return string != null && !string.isEmpty() && !string.equals("");
}
public static void linkPhone(String verifyCode1,String phone) {
Log.d("DDDD", " verifyCode1 "+verifyCode1);
String phoneNumber = phone;
String countCode = "+91";
String verifyCode = verifyCode1;
Log.e("DDDD", " verifyCode "+verifyCode);
AGConnectAuthCredential credential = PhoneAuthProvider.credentialWithVerifyCode(
countCode,
phoneNumber,
null, // password, can be null
verifyCode);
AGConnectAuth.getInstance().getCurrentUser().link(credential).addOnSuccessListener(new OnSuccessListener<SignInResult>() {
@Override
public void onSuccess(SignInResult signInResult) {
String phoneNumber = signInResult.getUser().getPhone();
String uid = signInResult.getUser().getUid();
Log.d("DDDD", "phone number: " + phoneNumber + ", uid: " + uid);
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.e("DDDD", "Login error, please try again, error:" + e.getMessage());
}
});
}
}
GameManager.cs
Code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameManager : MonoBehaviour
{
public InputField OtpField,inputFieldPhone;
string otp=null,phone="";
// Start is called before the first frame update
void Start()
{
inputFieldPhone.text = "9740424108";
}
public void onClickButton(){
phone = inputFieldPhone.text;
using (AndroidJavaClass javaClass = new AndroidJavaClass("com.huawei.AuthServiceDemo22.MainActivity"))
{
javaClass.CallStatic("sendVerifCode",phone);
}
}
public void LinkPhone(){
otp = OtpField.text;
Debug.Log(" OTP "+otp);
using (AndroidJavaClass javaClass = new AndroidJavaClass("com.huawei.AuthServiceDemo22.MainActivity"))
{
javaClass.CallStatic("linkPhone",otp,phone);
}
}
public void AnoniomousLogin(){
using (AndroidJavaClass javaClass = new AndroidJavaClass("com.huawei.AuthServiceDemo22.MainActivity"))
{
javaClass.CallStatic("AnoniomousLogin");
}
}
}
10. Click to Build apk, choose File > Build settings > Build, to Build and Run, choose File > Build settings > Build And Run.
Result
Tips and Tricks
Add agconnect-services.json file without fail.
Make sure dependencies added in build files.
Make sure that you enabled the Auth Service in AG-Console.
Make sure that you enabled the Authentication mode in Auth Service.
Conclusion
We have learnt integration of Huawei Auth Service-AGC anonymous account login and mobile number verification through OTP in Unity Game development. Conclusion is Auth Service provides secure and reliable user authentication system to your application.
Thank you so much for reading article, hope this article helps you.
Reference
Official documentation service introduction
Unity Auth Service Manual
Auth Service CodeLabs
Checkout in forum

Intermediate: How to extract table information from Images using Huawei HiAI Table Recognition service in Android

Introduction
In this article, we will learn how to implement Huawei HiAI kit using Table Recognition service into android application, this service helps us to extract the table content from images.
Table recognition algorithms, this one is based on the line structure of table. Clear and detectable lines are necessary for the proper identification of cells.
Use case: Imagine you have lots of paperwork and documents where you would be using tables, and using the same you would like to manipulate data. Conventionally you can copy them manually or generate excel files for third party apps.
{
"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"
}
Requirements
1. Any operating system (MacOS, Linux and Windows).
2. Any IDE with Android SDK installed (IntelliJ, Android Studio).
3. HiAI SDK.
4. Minimum API Level 23 is required.
5. Required EMUI 9.0.0 and later version devices.
6. Required process kirin 990/985/980/970/ 825Full/820Full/810Full/ 720Full/710Full
Features
1. Restores the table information including text in the cells, and identifies merged cells as well.
2. Fast recognition it returns the text in a cell containing 50 lines within 3seconds
3. Recognition accuracy level >85%
4. Recall rate >80%
How to integrate HMS Dependencies
1. First of all, we need to create an app on AppGallery Connect and add related details about HMS Core to our project. For more information check this link
2. Download agconnect-services.json file from AGC and add into app’s root directory.
3. Add the required dependencies to the build.gradle file under root folder.
Code:
maven {url'https://developer.huawei.com/repo/'}
classpath 'com.huawei.agconnect:agcp:1.4.1.300'
4. Add the App level dependencies to the build.gradle file under app folder.
Code:
apply plugin: 'com.huawei.agconnect'
5. Add the required permission to the Manifestfile.xml file.
Code:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.hardware.camera"/>
<uses-permission android:name="android.permission.HARDWARE_TEST.camera.autofocus"/>
6. Now, sync your project.
How to apply for HiAI Engine Library
1. Navigate to this URL, choose App Service > Development and click HUAWEI HiAI.
2. Click Apply for HUAWEI HiAI kit.
3. Enter required information like Product name and Package name, click Next button.
4. Verify the application details and click Submit button.
5. Click the Download SDK button to open the SDK list.
6. Unzip downloaded SDK and add into your android project under lib folder.
7. Add jar files dependences into app build.gradle file.
Code:
implementation fileTree(<b><span style="font-size: 10.0pt;font-family: Consolas;">include</span></b>: [<b><span style="font-size: 10.0pt;">'*.aar'</span></b>, <b><span style="font-size: 10.0pt;">'*.jar'</span></b>], <b><span style="font-size: 10.0pt;">dir</span></b>: <b><span style="font-size: 10.0pt;">'libs'</span></b>)
implementation <b><span style="font-size: 10.0pt;">'com.google.code.gson:gson:2.8.6'
repositories {
flatDir {
dirs 'libs'
}
}</span></b><b style="font-family: Consolas;font-size: 10.0pt;background-color: white;">
</b>
8. After completing this above setup, now Sync your gradle file.
Let’s do code
I have created a project on Android studio with empty activity let’s start coding.
In the MainActivity.java we can create the business logic.
Code:
MainActivity extends AppCompatActivity {
private boolean isConnection = false;
private int REQUEST_CODE = 101;
private int REQUEST_PHOTO = 100;
private Bitmap bitmap;
private Bitmap resultBitmap;
private Button btnImage;
private ImageView originalImage;
private ImageView conversionImage;
private TextView textView;
private TextView contentText;
private final String[] permission = {
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE};
private ImageSuperResolution resolution;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
requestPermissions(permission, REQUEST_CODE);
initHiAI();
originalImage = findViewById(R.id.super_origin);
conversionImage = findViewById(R.id.super_image);
textView = findViewById(R.id.text);
contentText = findViewById(R.id.content_text);
btnImage = findViewById(R.id.btn_album);
btnImage.setOnClickListener(v -> {
selectImage();
});
}
private void initHiAI() {
VisionBase.init(this, new ConnectionCallback() {
@Override
public void onServiceConnect() {
isConnection = true;
DeviceCompatibility();
}
@Override
public void onServiceDisconnect() {
}
});
}
private void DeviceCompatibility() {
resolution = new ImageSuperResolution(this);
int support = resolution.getAvailability();
if (support == 0) {
Toast.makeText(this, "Device supports HiAI Image super resolution service", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Device doesn't supports HiAI Image super resolution service", Toast.LENGTH_SHORT).show();
}
}
public void selectImage() {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
startActivityForResult(intent, REQUEST_PHOTO);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
if (data != null && requestCode == REQUEST_PHOTO) {
try {
bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), data.getData());
if (isConnection) {
setTableAI();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
private void setTableAI() {
textView.setText("Extraction Table Text");
contentText.setVisibility(View.VISIBLE);
TableDetector mTableDetector = new TableDetector(this);
VisionImage image = VisionImage.fromBitmap(bitmap);
VisionTableConfiguration mTableConfig = new VisionTableConfiguration.Builder()
.setAppType(VisionTableConfiguration.APP_NORMAL)
.setProcessMode(VisionTableConfiguration.MODE_OUT)
.build();
mTableDetector.setVisionConfiguration(mTableConfig);
mTableDetector.prepare();
Table table = new Table();
int mResult_code = mTableDetector.detect(image, table, null);
if (mResult_code == 0) {
int count = table.getTableCount();
List<TableContent> tc = table.getTableContent();
StringBuilder sbTableCell = new StringBuilder();
List<TableCell> tableCell = tc.get(0).getBody();
for (TableCell c : tableCell) {
List<String> words = c.getWord();
StringBuilder sb = new StringBuilder();
for (String s : words) {
sb.append(s).append(",");
}
String cell = c.getStartRow() + ":" + c.getEndRow() + ": " + c.getStartColumn() + ":" +
c.getEndColumn() + "; " + sb.toString();
sbTableCell.append(cell).append("\n");
contentText.setText("Count = " + count + "\n\n" + sbTableCell.toString());
}
}
}
}
Demo
Tips and Tricks
1. Download latest Huawei HiAI SDK.
2. Set minSDK version to 23 or later.
3. Do not forget to add jar files into gradle file.
4. It supports slides images.
5. Input resolution larger than 720p and with aspect ratio smaller than 2:1.
6. It supports only printed text, images, formulas, handwritten content, seals, watermarks cannot be identified.
7. Refer this URL for supported Countries/Regions list.
Conclusion
That’s it! Now your table content extracted from image, for further analysis with statistics or just for editing it. This works for tables with clear and simple structure information.
Thanks for reading! If you enjoyed this story, please click the Like button and Follow. Feel free to leave a Comment below.
Reference
Huawei HiAI Table Recognition Kit URL
Original Source

Categories

Resources