Intermediate - Create Smart Search App with Huawei Search Kit in Kotlin - Huawei Developers

Overview
Smart search application provides user to search capabilities through the device-side SDK and cloud-side APIs. Moreover this app results the selection search of Web Page Search, Image Search, Video Search and News Search. To achieve this the app is using HMS Search kit.
Huawei Search Kit
HUAWEI Search Kit fully opens Petal Search capabilities through the device-side SDK and cloud-side APIs, enabling ecosystem partners to quickly provide the optimal mobile app search experience.
Advantages of Using HMS Search kit
Rapid app development
Opens search capabilities through the device-side SDK and cloud-side APIs for different kinds of apps to build their own search capabilities within a short period of time.
Quick response
Allows you to use the global index library built by Petal Search and its massive computing and storage resources to quickly return the search results from the destination site that you specified.
Low cost
Frees you from having to consider such problems as technical implementation, operations, and maintenance, thus decreasing your development cost.
Integration Steps
1) Create an app in App Gallery Connect (AGC)
2) Enable Search kit from Manage Apis.
{
"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"
}
3) Add SHA-256 certificate fingerprint , Set Data storage location.
4) Now download the agconnect-service.json file from app information and paste in your app folder in android studio.
5) Add the maven url in buildscript and all projects in project level gradle file.
Java:
maven {url 'https://developer.huawei.com/repo/'}
6) Add the below dependencies in app level gradle file.
Java:
implementation 'com.huawei.hms:searchkit:5.0.4.303'
7) Set minimum SDK version as 24.
Java:
android {
defaultConfig {
minSdkVersion 24
}
}
8) Create Application class and set the AppID.
Java:
class ApplicationClass: Application() {
override fun onCreate() {
super.onCreate()
SearchKitInstance.init(this, Constants.APP_ID)
}
}
9) Now sync the gradle.
Development
1. Web Page Search
This explains how to use web page search and in this you can set the restriction for the search such as Language,Region, Page number and the number of search results to be displayed.
Add this code in the MainActivity and call this by passing searchText whenever the user search using WebSearch.
Java:
fun webSearch(searchText: String, token: String) : ArrayList<WebItem>{
val webSearchRequest = WebSearchRequest()
webSearchRequest.setQ(searchText)
webSearchRequest.setLang(Language.ENGLISH)
webSearchRequest.setSregion(Region.UNITEDKINGDOM)
webSearchRequest.setPs(10)
webSearchRequest.setPn(1)
SearchKitInstance.getInstance().setInstanceCredential(token)
val webSearchResponse = SearchKitInstance.getInstance().webSearcher.search(webSearchRequest)
for(i in webSearchResponse.getData()){
webResults.clear()
webResults.add(i)
}
return webResults
}
2. Image Search
This explains how to use Image search and the expected result will be in Image. Also here you can set the restrictions to the search like specify the region, the language for the search, the page number and the number of search results to be returned on the page.
Add this code in the MainActivity and call this by passing searchText whenever the user search using ImageSearch.
Java:
fun imageSearch(searchText: String, token: String) : ArrayList<ImageItem>{
val commonSearchRequest = CommonSearchRequest()
commonSearchRequest.setQ(searchText)
commonSearchRequest.setLang(Language.ENGLISH)
commonSearchRequest.setSregion(Region.UNITEDKINGDOM)
commonSearchRequest.setPs(10)
commonSearchRequest.setPn(1)
SearchKitInstance.getInstance().setInstanceCredential(token)
val imageSearchResponse = SearchKitInstance.getInstance().imageSearcher.search(commonSearchRequest)
for(i in imageSearchResponse.getData()) {
imageResults.clear()
imageResults.add(i)
}
return imageResults
}
3. Video Search
This explains how to use Video search and the expected result will be in the Video format. Also here you can set the restrictions to the search like specify the region, the language for the search, the page number and the number of search results to be returned on the page.
Add this code in the MainActivity and call this by passing searchText whenever the user search using VideoSearch.
Java:
fun videoSearch(searchText: String, token: String) : ArrayList<VideoItem>{
val commonSearchRequest = CommonSearchRequest()
commonSearchRequest.setQ(searchText)
commonSearchRequest.setLang(Language.ENGLISH)
commonSearchRequest.setSregion(Region.UNITEDKINGDOM)
commonSearchRequest.setPs(10)
commonSearchRequest.setPn(1)
SearchKitInstance.getInstance().setInstanceCredential(token)
val videoSearchResponse = SearchKitInstance.getInstance().videoSearcher.search(commonSearchRequest)
for(i in videoSearchResponse.getData()) {
videoResults.clear()
videoResults.add(i)
}
return videoResults
}
4. News Search
This explains how to use News search in your application. Also here you can set the restrictions to the search like specify the region, the language for the search, the page number and the number of search results to be returned on the page.
Add this code in the MainActivity and call this by passing searchText whenever the user search using NewsSearch.
Java:
fun newsSearch(searchText: String, token: String) : ArrayList<NewsItem>{
val commonSearchRequest = CommonSearchRequest()
commonSearchRequest.setQ(searchText)
commonSearchRequest.setLang(Language.ENGLISH)
commonSearchRequest.setSregion(Region.UNITEDKINGDOM)
commonSearchRequest.setPs(10)
commonSearchRequest.setPn(1)
SearchKitInstance.getInstance().setInstanceCredential(token)
val newsSearchResponse = SearchKitInstance.getInstance().newsSearcher.search(commonSearchRequest)
for(i in newsSearchResponse.getData()) {
newsResults.clear()
newsResults.add(i)
}
return newsResults
}
5. activity_main.xml
XML:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.SearchActivity">
<RelativeLayout
android:id="@+id/searchview_layout"
android:layout_height="36dp"
android:layout_width="match_parent"
android:focusable="true"
android:focusableInTouchMode="true"
android:layout_marginTop="10dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp">
<EditText
android:id="@+id/search_view"
android:layout_width="match_parent"
android:layout_height="36dp"
android:background="@drawable/shape_search_text"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center_vertical|start"
android:hint="Search"
android:imeOptions="actionSearch"
android:paddingStart="42dp"
android:paddingEnd="40dp"
android:singleLine="true"
android:ellipsize="end"
android:maxEms="13"
android:textAlignment="viewStart"
android:textColor="#000000"
android:textColorHint="#61000000"
android:textCursorDrawable="@drawable/selectedittextshape"
android:textSize="16sp" />
<ImageView
android:id="@+id/search_src_icon"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginStart="3dp"
android:clickable="false"
android:focusable="false"
android:padding="10dp"
android:src="@drawable/ic_public_search" />
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/suggest_list"
android:visibility="gone"
android:layout_below="@+id/searchview_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.recyclerview.widget.RecyclerView>
<LinearLayout
android:id="@+id/linear_spell_check"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_below="@+id/searchview_layout"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="spellCheck:"
android:textColor="#000000"
android:textSize="14sp"
android:layout_gravity="center_vertical"
android:gravity="center"
android:layout_marginLeft="20dp"/>
<TextView
android:id="@+id/tv_spell_check"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textColor="#000000"
android:textSize="14sp"
android:layout_gravity="center_vertical"
android:gravity="center"
android:layout_marginLeft="10dp"/>
</LinearLayout>
<View
android:id="@+id/v_line"
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_marginTop="10dp"
android:layout_below="@+id/linear_spell_check"
android:background="#e2e2e2"/>
<LinearLayout
android:id="@+id/linear_view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/v_line"
android:orientation="vertical">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_gravity="center"
app:tabIndicatorColor="#0000ad"
app:tabIndicatorFullWidth="false"
app:tabIndicatorHeight="2dp"
app:tabMode="scrollable"
app:tabRippleColor="#0D000000"
app:tabSelectedTextColor="#0000ad">
</com.google.android.material.tabs.TabLayout>
<androidx.viewpager.widget.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/content_list"
android:visibility="gone"
android:layout_below="@+id/v_line"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.recyclerview.widget.RecyclerView>
</RelativeLayout>
Now the implementation is done. From the search bar edit text you can call any of the respective search method and obtain the result as expected.
Result
Tips and Tricks
1) Do not forget to add the AppID in Application class.
2) Maintain minSdkVersion 24 and above.
Conclusion
This application is very much useful for those who wants to create a search application using HMS Search kit. Moreover it helps to understand the usage of HMS Search kit and to get the appropriate search results.
Reference
https://developer.huawei.com/consum.../HMSCore-Guides/introduction-0000001055591730

Can we customize search parameters based on requirement?

Related

HiAi – Category Label, How to get information based on deep learning

More information like this, you can visit HUAWEI Developer Forum​
Take a photo and automatically get a infromations from the object in there, the scenes presented an behaiviors, this can be very usefull if you need to categorize your images and photos based on deep learning, this is something that we can do with HiAI - category label capability, in this post, you will see how to do it with a JAVA project.
{
"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"
}
HIAI has serveral categories, base on deep learning you will be able to get a category that is identified in your images.
if you want to get the full information you will get it on the API reference in the next link: https://developer.huawei.com/consumer/en/doc/development/hiai-References/20201004
What do we need :
Java JDK installation package
Android Studio 3.1 or later
Android SDK package
HiAI SDK package
1) Install DevEco IDE Plugins
Into your Android istudio go to File>Settings>Plugins and search for Dev ECO ID and Enable it \m/
Once is enable, dont forget to Restart you IDE (Android Studio).
2) Create your APP
3) Add dependency library
· Download the Huawei-hiai-vision.aar package in the Huawei AI Engine SDKs from the Huawei developer community.
· Copy the downloaded vision-release.aar package to the app/libs directory of the project.
4) Lets code
I will create a project on android studio starting with an empty activity, for the visual side i will add an imageView to show the photo, a button to take the photo and a textview to show the different value sthat i can get for the category from HIAI, for the logic side i will use the HiAi methods to make the API call to get the Category Label of the photos, the steps that we need are the following:
 -Define the layout
Code:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.CategoryLabel">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraintlayout"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintVertical_bias="0.5"
app:layout_constraintBottom_toTopOf="@+id/linearlayout">
<com.huawei.hiaicodedemo.widget.TitleBar
android:id="@+id/titleBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:titleName="@string/as_catLabel"
app:titleTextColor="@color/black"
app:titleTextSize="18sp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
<ImageView
android:id="@+id/text_origin"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="30dp"
android:src="@color/color_6e"
app:layout_constraintDimensionRatio="h,4:3"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintWidth_percent="0.8"
app:layout_constraintTop_toBottomOf="@+id/titleBar"
app:layout_constraintBottom_toTopOf="@+id/after"/>
<LinearLayout
android:id="@+id/after"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="30dp"
android:layout_marginRight="30dp"
android:layout_marginBottom="15dp"
android:text="@string/as_result"
android:textSize="18sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/color_6e"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:id="@+id/linearlayout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="@color/color_6e"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintVertical_bias="0.5"
app:layout_constraintTop_toBottomOf="@+id/constraintlayout">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white">
<androidx.core.widget.NestedScrollView
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="h,4:3"
app:layout_constraintWidth_percent="0.8"
android:layout_marginTop="15dp"
android:layout_marginBottom="15dp"
android:background="@color/color_6e">
<EditText
android:id="@+id/text_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:textColor="@color/black" />
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/takePhoto"
android:layout_width="90dp"
android:layout_height="37dp"
android:layout_marginStart="48dp"
android:layout_marginTop="16dp"
android:background="@drawable/submit_button"
android:text="@string/foto"
android:textAllCaps="false"
android:textColor="@drawable/submit_button_text_color"
android:textSize="15sp"
app:layout_constraintHorizontal_bias="0.031"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/btn_album"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.37" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
 -Prepare the intent to get the picture from the camera and save it into a Bitmap
Code:
public void tomaFotoCategory(CategoryLabel view){
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, TAKE_FOTO);
}
Remember to add the permises to use the periferic in you manifest:
Code:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 -Connect to HiAi Engine to use the Category Label using VisionBase Class
Code:
private void initHiAI() {
VisionBase.init(this, new ConnectionCallback() {
@Override
public void onServiceConnect() {
}
@Override
public void onServiceDisconnect() {
}
});
}
 -Make the API CALL to get the Category Label, for this you need to declare a variable Frame & Label where you will get the response from the method
Code:
/** Define class detector, the context of this project is the input parameter*/
LabelDetector labelDetector = new LabelDetector(this);
/**Define the frame, put the bitmap that needs to detect the image into the frame*/
Frame frame = new Frame();
frame.setBitmap(bitmap);
/** Call the detect method to get the result of the label detection */
/** Note: This line of code must be placed in the worker thread instead of the main thread*/
JSONObject jsonLabel = labelDetector.detect(frame, null);
/** Call convertResult() method to convert the json to java class and get the label detection(you can parse the json by yourself, too) */
Label label1 = labelDetector.convertResult(jsonLabel);
this.label = label1.getCategory();
this.labelContents =label1.getLabelContent();
this.categoria=label1.getCategory();
this.prob=label1.getCategoryProbability();
handler.sendEmptyMessage(AESTHETICSCORE_RESULT);
 -To print the result into the text i will use the getters of all the values that i can get form the object Label, LabelContents, Category anf probablillity:
Code:
textEdit.setText("Label: "+String.valueOf(label)
+" \n Categoria: "+categoria
+" \n Label_ID: "+labelContents.get(0).getLabelId()
+" \n Prob: "+ String.valueOf(prob)
+" \n Probabilidad: "+labelContents.get(0).getProbability());
If you need the complete code project, let me know, in there you will be able to review all the parameter definition .
1) Once you have your DevEco IDE plugin Eneable you will see an AMAZING KIT ASSITANT
go to DevECO>EMUI KIT>KIT ASSITANT
2) You will see a Menu like this:
3) Select HiAi and look Aesthetic
4) BOOM!!! Code Samples, Descriptions, Suggestions, DRAG & DROP CODE!!!!
Original link: https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0201322052049010285&fid=0101187876626530001

HiAi - Scene Detection, How to categorize images by identifying scenes of your images

More information like this, you can visit HUAWEI Developer Forum​
Original link: https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0201333475840980029&fid=0101187876626530001
You take a photo and you want to automatically identify which scene is by an intelligent recognize, or maybe you want to categorize you photo album by scenes, this is possible by a HIAI engine by a quick scene clasification.
On this post i will show you how you can use this feature on a Java-native app, since the front activities until the methos from HIAI you must call.
{
"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"
}
HiAi Engine can recognize several scenes automatically bby using its methods, here some examples:
if you want to get the full information you will get it on the API reference in the next link: https://developer.huawei.com/consumer/en/doc/development/hiai-References/20201204
What do we need :
Java JDK installation package
Android Studio 3.1 or later
Android SDK package
HiAI SDK package
1) Install DevEco IDE Plugins
Into your Android istudio go to File>Settings>Plugins and search for Dev ECO ID and Enable it \m/
Once is enable, dont forget to Restart you IDE (Android Studio).
2) Create your APP
3) Add dependency library
· Download the Huawei-hiai-vision.aar package in the Huawei AI Engine SDKs from the Huawei developer community.
Copy the downloaded vision-release.aar package to the app/libs directory of the project.
4) Lets code
I will create a project on android studio starting with an empty activity, for the visual side i will add an imageView to show the photo, a button to take the photo and a textview to show the different values that i can get for the scene, for the logic side i will use the HiAi methods to make the API call to get the image scene of the photos.
  -Define the layout
Code:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.SceneDetection">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraintlayout"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintVertical_bias="0.5"
app:layout_constraintBottom_toTopOf="@+id/linearlayout">
<com.huawei.hiaicodedemo.widget.TitleBar
android:id="@+id/titleBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:titleName="@string/as_scene"
app:titleTextColor="@color/black"
app:titleTextSize="18sp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
<ImageView
android:id="@+id/text_origin"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="30dp"
android:src="@color/color_6e"
app:layout_constraintDimensionRatio="h,4:3"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintWidth_percent="0.8"
app:layout_constraintTop_toBottomOf="@+id/titleBar"
app:layout_constraintBottom_toTopOf="@+id/after"/>
<LinearLayout
android:id="@+id/after"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="30dp"
android:layout_marginRight="30dp"
android:layout_marginBottom="15dp"
android:text="@string/as_scene"
android:textSize="18sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/color_6e"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:id="@+id/linearlayout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="@color/color_6e"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintVertical_bias="0.5"
app:layout_constraintTop_toBottomOf="@+id/constraintlayout">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white">
<androidx.core.widget.NestedScrollView
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="h,4:3"
app:layout_constraintWidth_percent="0.8"
android:layout_marginTop="15dp"
android:layout_marginBottom="15dp"
android:background="@color/color_6e">
<EditText
android:id="@+id/text_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:textColor="@color/black" />
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/takePhoto"
android:layout_width="90dp"
android:layout_height="37dp"
android:layout_marginStart="48dp"
android:layout_marginTop="16dp"
android:background="@drawable/submit_button"
android:text="@string/foto"
android:textAllCaps="false"
android:textColor="@drawable/submit_button_text_color"
android:textSize="15sp"
app:layout_constraintHorizontal_bias="0.031"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/btn_album"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.37" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
 -Prepare the intent to get the picture from the camera and save it into a Bitmap
Code:
public void tomaFotoCategory(CategoryLabel view){
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, TAKE_FOTO);
}
Remember to add the permises to use the periferic in you manifest:
Code:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 -Connect to HiAi Engine to use the scene detection by VisionBase Class
In the class where you are going to call the methos to use the HIAI, we have to init the HIAI capability, i have created a method to call this init as following:
Code:
private void initHiAI() {
VisionBase.init(this, new ConnectionCallback() {
@Override
public void onServiceConnect() {
}
@Override
public void onServiceDisconnect() {
}
});
}
 -lets get the scene
Now, to get the scene category from your photo, you will have to make an instance from the class SceneDetectior and using its detect() method the value can be identified, in my case i create a method to do this job, every step is decribed in my code bellow:
Code:
private void setHiAi() {
/** Define class detector, the context of this project is the input parameter: */
SceneDetector sceneDetector = new SceneDetector(this);
/** define frame class, put the picture which need to be scene detected into the frame */
Frame frame = new Frame();
/** BitmapFactory.decodeFile input resource file path*/
frame.setBitmap(bitmap);
/** Call the detect method to get the result of the scene detection */
/** Note: This line of code must be placed in the worker thread instead of the main thread */
JSONObject jsonScene = sceneDetector.detect(frame, null);
/** Call convertResult() method to convert the json to java class and get the label detection (you can parse the json by yourself, too) */
Scene scene = sceneDetector.convertResult(jsonScene);
/** Get the identified scene type*/
int type = scene.getType();
this.resultado = type;
handler.sendEmptyMessage(TEXTDETECTOR_RESULT);
}
The result is the following (if you need the full project, let me know in the comments):
---------------------- < BONUS TRACK /> --------------------
1) Once you have your DevEco IDE plugin Eneable you will see an AMAZING KIT ASSITANT
go to DevECO>EMUI KIT>KIT ASSITANT
2) You will see a Menu like this:
3) Select HiAi and search for scene detection:
4) BOOM!!! Code Samples, Descriptions, Suggestions, DRAG & DROP CODE!!!!
How much working days it will take to integrate ?
This is very nice, it helps a lot.
If you have a big library is very useful. It will be very awesome if you could use it with movies.

Android Deep Link Using Huawei App Gallery Ability

HMS App Linking Introduction
{
"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"
}
HMS App Linking allows you to create cross-platform links that can work as defined regardless of whether your app has been installed by a user. When a user taps the link on an Android or iOS device, the user will be redirected to the specified in-app content. If a user taps the link in a browser, the user will be redirected to the same content of the web version.
To identify the source of a user, you can set tracing parameters for various channels when creating a link of App Linking to trace traffic sources. By analyzing the link performance of each traffic source based on the tracing parameters, you can find the platform that can achieve better promotion effect for your app:
Deferred deep link: Directs a user who has not installed your app to AppGallery to download your app first and then navigate to the link in-app content directly, without requiring the user to tap the link again.
Link display in card form: Uses a social Meta tag to display a link of App Linking as a card, which will attract more users from social media.
Statistics: Records the data of all link-related events, such as numbers of link taps, first app launches, and non-first app launches for you to conduct analysis.
API Overview
1. (Mandatory) Call AppLinking.Builder to create a Builder object.
2. (Mandatory) Call AppLinking.Builder.setUriPrefix to set the URL prefix that has been applied for in Applying for a URL Prefix.
3. (Mandatory) Call AppLinking.Builder.setDeepLink to set a deep link.
4. Call AppLinking.Builder.setAndroidLinkInfo to set Android app parameters. In this method, Android app parameters are contained in an AppLinking.AndroidLinkInfo instance, which can be built by calling AppLinking.AndroidLinkInfo.Builder. If this method is not called, the link will be opened in the browser by default.
5. Call AppLinking.Builder.setIOSLinkInfo to set iOS app parameters. In this method, iOS app parameters are contained in an AppLinking.IOSLinkInfo instance, which can be built by calling AppLinking.IOSLinkInfo.Builder. If this method is not called, the link will be opened in the browser by default.
6. Call AppLinking.IOSLinkInfo.Builder.setITunesConnectCampaignInfo to set App Store Connect campaign parameters. In this method, App Store Connect campaign parameters are contained in an AppLinking.ITunesConnectCampaignInfo instance, which can be built by calling AppLinking.ITunesConnectCampaignInfo.Builder.
7. Call AppLinking.Builder.setPreviewType to set the link preview type. If this method is not called, the preview page with app information is displayed by default.
8. Call AppLinking.Builder.setSocialCardInfo to set social Meta tags. In this method, social Meta tags are contained in an AppLinking.SocialCardInfo instance, which can be built by calling AppLinking.SocialCardInfo.Builder. If this method is not called, links will not be displayed as cards during social sharing.
9. Call AppLinking.Builder.setCampaignInfo to set ad tracing parameters. In this method, campaign parameters are contained in an AppLinking.CampaignInfo instance, which can be built by calling AppLinking.CampaignInfo.Builder.
Key Concepts
URL prefix
The URL prefix is the domain name contained in a link, which is in https://Domain name format. You can use the domain name provided by AppGallery Connect for free.
Long link
A long link is a link of App Linking in its entirety. follows this format:
URL prefix+Deep link+Android app parameters+iOS app parameters+[Preview type]+[Social meta tag]+[Tracing parameters]+[Landing page display parameter]+[Site ID].
An example of a long link is as follows:
https://yourapp.drcn.agconnect.link/?deeplink=https%3A%2F%2Fyourdeeplink.com&android_deeplink=https%3A%2F%2Fyourdeeplink.com&android_open_type=1&android_package_name=tiantian.huawei&campaign_channel=huawei&campaign_medium=pic&campaign_name=%E4%BF%83%E9%94%80&ios_link=https%3A%2F%2Fyourdeeplink.com&ios_bundle_id=aapp.huawei&at=1234&pt=212&ct=1234&mt=1&preview_type=2&social_title=%E6%8E%A5%E5%85%A5%E4%BF%83%E9%94%80&landing_page_type=1&&region_id=0
Short link
If a long link is too long, it can be converted to a short link. A short link follows this format:
URL prefix+Random suffix of the string type
Prerequisite
Huawei Phone
Android Studio
AppGallery Account
App Development
Create a New Project.
Configure Project Gradle.
Code:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
//TODO
maven { url 'https://developer.huawei.com/repo/' }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'
//TODO
classpath 'com.huawei.agconnect:agcp:1.4.2.301'
}
}
allprojects {
repositories {
google()
jcenter()
//TODO
maven { url 'https://developer.huawei.com/repo/' }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
3. Configure App Gradle.
Code:
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.cardview:cardview:1.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
//TODO
implementation 'com.huawei.agconnect:agconnect-applinking:1.4.2.301'
implementation 'com.huawei.hms:hianalytics:5.0.5.301'
4. Configure AndroidManifest.
Code:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hms.applinkandroid">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".applinking.DetailActivity"
android:configChanges="orientation|keyboardHidden|screenSize" />
<activity
android:name=".applinking.SettingActivity"
android:configChanges="orientation|keyboardHidden|screenSize" />
</application>
</manifest>
5. Create Activity class with XML UI.
MainActivity.java:
This activity performs all the operation of AppLinking with short and long url with Share card.
Code:
package com.hms.applinkandroid;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import com.hms.applinkandroid.applinking.DetailActivity;
import com.hms.applinkandroid.applinking.SettingActivity;
import com.huawei.agconnect.applinking.AGConnectAppLinking;
import com.huawei.agconnect.applinking.AppLinking;
import com.huawei.agconnect.applinking.ShortAppLinking;
public class MainActivity extends AppCompatActivity {
private static final String DOMAIN_URI_PREFIX = "https://applinkingtest.drcn.agconnect.link";
private static final String DEEP_LINK = "https://developer.huawei.com/consumer/cn/doc/development/AppGallery-connect-Guides";
private TextView shortTextView;
private TextView longTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
shortTextView = findViewById(R.id.shortLinkText);
longTextView = findViewById(R.id.longLinkText);
findViewById(R.id.create)
.setOnClickListener(
view -> {
createAppLinking();
});
findViewById(R.id.shareShort)
.setOnClickListener(
view -> {
shareLink((String) shortTextView.getText());
});
findViewById(R.id.shareLong)
.setOnClickListener(
view -> {
shareLink((String) longTextView.getText());
});
AGConnectAppLinking.getInstance()
.getAppLinking(this)
.addOnSuccessListener(
resolvedLinkData -> {
Uri deepLink = null;
if (resolvedLinkData != null) {
deepLink = resolvedLinkData.getDeepLink();
}
TextView textView = findViewById(R.id.deepLink);
textView.setText(deepLink != null ? deepLink.toString() : "");
if (deepLink != null) {
String path = deepLink.getLastPathSegment();
if ("detail".equals(path)) {
Intent intent = new Intent(getBaseContext(), DetailActivity.class);
for (String name : deepLink.getQueryParameterNames()) {
intent.putExtra(name, deepLink.getQueryParameter(name));
}
startActivity(intent);
}
if ("setting".equals(path)) {
Intent intent = new Intent(getBaseContext(), SettingActivity.class);
for (String name : deepLink.getQueryParameterNames()) {
intent.putExtra(name, deepLink.getQueryParameter(name));
}
startActivity(intent);
}
}
})
.addOnFailureListener(
e -> {
Log.w("MainActivity", "getAppLinking:onFailure", e);
});
}
private void createAppLinking() {
AppLinking.Builder builder = new AppLinking.Builder()
.setUriPrefix(DOMAIN_URI_PREFIX)
.setDeepLink(Uri.parse(DEEP_LINK))
.setAndroidLinkInfo(new AppLinking.AndroidLinkInfo.Builder().build())
.setSocialCardInfo(new AppLinking.SocialCardInfo.Builder()
.setTitle("华为开发者大会")
.setImageUrl("https://developer.huawei.com/consumer/cn/events/hdc2020/img/kv-pc-cn.png?v0808")
.setDescription("Description").build())
.setCampaignInfo(new AppLinking.CampaignInfo.Builder()
.setName("HDC")
.setSource("AGC")
.setMedium("App").build());
longTextView.setText(builder.buildAppLinking().getUri().toString());
builder.buildShortAppLinking().addOnSuccessListener(shortAppLinking -> {
shortTextView.setText(shortAppLinking.getShortUrl().toString());
}).addOnFailureListener(e -> {
Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_LONG).show();
});
}
private void shareLink(String appLinking) {
if (appLinking != null) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, appLinking);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
private void showError(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
main_activity.xml:
Code:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/ic_bg"
android:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/TitleView"
android:layout_width="281dp"
android:layout_height="51dp"
android:text="Android Deep Link"
android:textColor="@android:color/white"
android:textSize="30dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.061" />
<androidx.cardview.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
card_view:cardElevation="5dp"
card_view:cardUseCompatPadding="true"
card_view:contentPadding="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/textView5"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Deeplink"
android:textColor="@android:color/black"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/deepLink"
android:layout_width="match_parent"
android:layout_height="59dp"
android:textColor="@android:color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.241" />
<Button
android:id="@+id/create"
android:layout_width="230dp"
android:layout_height="50dp"
android:text="Create App Linking"
android:textAllCaps="false"
android:textSize="19sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.324" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
card_view:cardElevation="5dp"
card_view:cardUseCompatPadding="true"
card_view:contentPadding="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/textView3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="Long App Linking:"
android:textColor="@android:color/black"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/longLinkText"
android:layout_width="match_parent"
android:layout_height="80dp"
android:gravity="center_horizontal" />
<Button
android:id="@+id/shareLong"
android:layout_width="230dp"
android:layout_height="50dp"
android:text="Share long App Linking"
android:textAllCaps="false"
android:textSize="19sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
card_view:cardElevation="5dp"
card_view:cardUseCompatPadding="true"
card_view:contentPadding="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="Short App Linking:"
android:textColor="@android:color/black"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/shortLinkText"
android:layout_width="match_parent"
android:layout_height="60dp"
android:gravity="center_horizontal" />
<Button
android:id="@+id/shareShort"
android:layout_width="230dp"
android:layout_height="50dp"
android:text="Share short App Linking"
android:textAllCaps="false"
android:textSize="19sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
</ScrollView>
App Gallery Integration process
Sign In and Create or Choose a project on AppGallery Connect portal.
Navigate to Project settings and download the configuration file.
Navigate to General Information, and then provide Data Storage location.
Navigate to Manage APIs and enable APIs which is required by application.
Navigate to AppLinking and Enable.
Add New link.
Navigate to App Linking and select Set domain name.
Copy Domain Name and add in your project.
App Build Result
Tips and Tricks
Since HUAWEI Analytics Kit 4.0.3.300, the SDK for Android has been significantly improved in the stability, security, and reliability. If the SDK you have integrated is earlier than 4.0.3.300, please upgrade it to 4.0.3.300 or later before April 30, 2021. From May 1, 2021, HUAWEI Analytics will not receive data reported by SDK versions earlier than 4.0.3.300. If you have integrated App Linking, you also need to upgrade its SDK to 1.4.1.300 or later for Android before April 30, 2021. Otherwise, functions that depend on HUAWEI Analytics will become unavailable.
Huawei strictly conforms to the General Data Protection Regulation (GDPR) in providing services and is dedicated to helping developers achieve business success under the principles of the GDPR. The GDPR stipulates the obligations of the data controller and data processor. When using our service, you act as the data controller, and Huawei is the data processor. Huawei solely processes data within the scope of the data processor's obligations and rights, and you must assume all obligations of the data controller as specified by the GDPR.
Conclusion
In this article, we have learned how to integrate AppLinking in application. In this application, I have explained that how to deep link our application with URL.
Thanks for reading this article. Be sure to like and comments to this article, if you found it helpful. It means a lot to me.
References
HMS AppLinking Doc: Link
Original Source

Expert: BestPal (Chatting) application using Huawei CloudDB, Auth service, Cloud Function, Location, Site, Map and Push Kits - Part 2

{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
In previous article, we have developed one to one text sending application. Now in this article, we will work on sent location in chat.
Previous article link: https://forum.xda-developers.com/t/...-service-cloud-function-and-push-kit.4398203/
Huawei Kits Used​
Huawei Cloud DB
Huawei Auth Service
Huawei Cloud function.
Huawei Push Kit
Location kit
Site kit
Map kit
Huawei API Used​
Huawei Cloud Storage - Cloud storage is used to store users files like user profile image and images shared on the chat. Once files are uploaded successfully on storage we get the downloadable URL will act like the profile URL or the image content.
Huawei CloudDB API - Cloud DB is used to store users data, users chat and also used to manage users chat history with other users.
a) Upsert
i) Insert data of the users from the profile.
ii) Create and insert room id, room id is consider as a reference between two users chat. Using room id we will store all the respective chat data in the DB.
iii) Insert Chat data between two users based on the room id.
b) Query
i) Get list of Contacts for chat.
ii) Get list of user with whom logged in user chatted before.
ii) Get details of the chat screen with all the chat messages which include, images, text and location.
3.Huawei Auth Service – Using the Auth Service we are registering the user on the Ecosystem. We are using the Phone number auth service for the same to receive the OTP and verify the user here.
4.Huawei Cloud function – We are triggering the Huawei Push notification system using cloud function for the same.
5.Huawei Location kit - Using location kit we will get users current location, so that they can share with other users. Using the same location co-ordinate, we will try to receive the nearby service shows the nearby landmark.
6.Huawei Map kit –
a. Map kit is used to show users shared location and nearby landmarks on the map.
b. Static Map Query after the getting the location we will query the map static with the marker and create the link and upload that link to the webview to show the current location being shared.
7.Huawei Site Kit –To show the landmark on the map which can be shared with different users at the given time of request for this we have used the nearby service from the site kit.
8.Huawei Push kit - Push kit is used to push notification of message to other user. So when one user send message we will notify other user via push notification only.
Used the rest end point for the cloud function to send the push notification once the message is end trigger from the device.
On HMSMessage Received This is once parsing the data as per our need on the implementation wherein we will need to parse and image and location when shared by other success.
Let’s start send location in chat
Enable Location Kit , Site Kit and Map Kit on console as shown in below image.
Add dependencies
Code:
// HMS dependencies
implementation "com.huawei.hms:site:$rootProject.ext.sitekit"
implementation "com.huawei.hms:maps:$rootProject.ext.mapkit"
implementation "com.huawei.hms:location:$rootProject.ext.locationkit"
Add run time location permission
Java:
private boolean checkLocationPermission() {
int location_permission = ContextCompat.checkSelfPermission(this.getApplicationContext(), Manifest.permission.ACCESS_FINE_LOCATION);
int course_permission = ContextCompat.checkSelfPermission(this.getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION);
return location_permission == PackageManager.PERMISSION_GRANTED && course_permission == PackageManager.PERMISSION_GRANTED;
}
Let's design the page
We divided page into two parts one is for map view and other half is for showing nearby places.
XML
XML:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:weightSum="2">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight=".15"
android:orientation="horizontal">
<ImageView
android:id="@+id/imageLocation"
android:layout_width="40dp"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:padding="5dp"
android:src="@drawable/ic_baseline_arrow_back" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_centerVertical="true"
android:text="@string/send_loc"
android:textColor="@color/black"
android:textSize="18sp"
android:textStyle="bold" />
<ImageView
android:layout_width="40dp"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:layout_marginEnd="10dp"
android:padding="5dp"
android:src="@drawable/hwsearchview_ic_public_input_search"
android:visibility="gone" />
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight=".8"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.huawei.hms.maps.MapView xmlns:map="http://schemas.android.com/apk/res-auto"
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent"
map:cameraTargetLat="51"
map:cameraTargetLng="10"
map:cameraZoom="8.5"
map:mapType="normal"
map:uiCompass="true"
map:uiZoomControls="true" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btnHospital"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_weight="1"
android:text="@string/btnHospital" />
<Button
android:id="@+id/btnAtm"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/btnHospital"
android:layout_weight="1"
android:text="@string/btnAtm" />
<Button
android:id="@+id/btnPetrolBunk"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/btnAtm"
android:layout_weight="1"
android:text="@string/btnPetrol" />
</LinearLayout>
</FrameLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.05"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_gravity="bottom"
android:background="#D3D3D3"
android:gravity="center_vertical"
android:paddingStart="5dp"
android:text="@string/nearby_places"
android:textColor="@color/black"
android:textSize="16sp" />
<RelativeLayout
android:id="@+id/rlSendCurrentLocation"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginStart="20dp"
android:paddingStart="5dp">
<ImageView
android:id="@+id/currentlocation_icon_iv"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_centerVertical="true"
android:scaleType="fitXY"
android:src="@drawable/ic_baseline_location"
android:tint="@color/colorPrimary"
tools:ignore="UseAppTint" />
<TextView
android:id="@+id/currnet_location_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="8dp"
android:layout_toEndOf="@id/currentlocation_icon_iv"
android:text="@string/send_location"
android:textColor="@color/black"
android:textSize="18sp" />
<TextView
android:id="@+id/currnet_location_accurecy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/currnet_location_title"
android:layout_marginStart="10dp"
android:layout_marginTop="1dp"
android:layout_toEndOf="@id/currentlocation_icon_iv"
android:text="@string/accuracy"
android:textColor="@color/grey"
android:textSize="14sp" />
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvNearByLocation"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:id="@+id/response_text_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="true" />
</LinearLayout>
</LinearLayout>
It's time to start coding
Get Current location
Java:
FusedLocationProviderClient mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context);
SettingsClient mSettingsClient = LocationServices.getSettingsClient(context);
LocationRequest mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(5000);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mFusedLocationProviderClient.getLastLocation().addOnSuccessListener(location -> {
AppLog.logD(TAG,
"Lat long--->Fushed" + location.getLongitude()
+ "," + location.getLatitude() + "," + location.getAccuracy());
if (location != null) {
locationMutableLiveData.postValue(location);
}
}).addOnFailureListener(e -> {
AppLog.logE(TAG, "error" + e);
Toast.makeText(context, "Location not found", Toast.LENGTH_SHORT).show();
});
Get Nearby location
Java:
public void getNearbyData(double latitude, double longitude, SearchService searchService, String locationType) {
NearbySearchRequest request = new NearbySearchRequest();
Coordinate location = new Coordinate(latitude, longitude);
request.setLocation(location);
request.setQuery(locationType);
request.setRadius(5);
request.setHwPoiType(HwLocationType.ADDRESS);
request.setLanguage("en");
request.setPageIndex(1);
request.setPageSize(10);
request.setStrictBounds(false);
SearchResultListener<NearbySearchResponse> resultListener = new SearchResultListener<NearbySearchResponse>() {
@Override
public void onSearchResult(NearbySearchResponse results) {
arrayListMutableLiveData.postValue(new ArrayList<>(results.getSites()));
}
@Override
public void onSearchError(SearchStatus status) {
AppLog.logE("TAG", "Error : " + status.getErrorCode() + " " + status.getErrorMessage());
}
};
searchService.nearbySearch(request, resultListener);
}
Set up Map and showing nearby places
Java:
@Override
public void onMapReady(HuaweiMap huaweiMap) {
hMap = huaweiMap;
hMap.setMyLocationEnabled(true);
hMap.getUiSettings().setMyLocationButtonEnabled(true);
Util.showProgressBar(LocationActivity.this);
locationViewModel.getCurrentLocation(LocationActivity.this);
locationViewModel.locationMutableLiveData.observe(LocationActivity.this, location -> {
if (location != null) {
this.location = location;
updateDetails(location);
}
});
locationViewModel.arrayListMutableLiveData.observe(LocationActivity.this, this::recyclerView);
}
private void updateDetails(Location location) {
float zoom = 14.0f;
LatLng latLng1 = new LatLng(location.getLatitude(), location.getLongitude());
CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(latLng1, zoom);
hMap.animateCamera(cameraUpdate);
currentLocationAccuracy.setText(String.format("Accurate to %s meters", location.getAccuracy()));
locationViewModel.getNearbyData(location.getLatitude(), location.getLongitude(), searchService, Constants.LOCATION_TYPE_HOSPITAL);
}
Send chat massage
Java:
private void setMessage(String messageType) {
ChitChatSharedPref.initializeInstance(MessageActivity.this);
Util.showProgressBar(MessageActivity.this);
UserChat userChat = new UserChat();
userChat.setRoom_id(roomId);
userChat.setMessage_timestamp(Long.parseLong(Util.getTimeStamp()));
userChat.setChat_id(Util.getRandomNumber());
userChat.setReceiver_name(receiverText);
userChat.setReceiver_phone(receiverPhoneNumber);
userChat.setSender_name(ChitChatSharedPref.getInstance().getString(Constants.USER_NAME, ""));
userChat.setSender_phone(ChitChatSharedPref.getInstance().getString(Constants.PHONE_NUMBER, ""));
userChat.setMessage_type(messageType);
switch (messageType) {
case Constants.MESSAGE_TYPE_MAP:
userChat.setMessage_data(jsonMapModel);
messageViewModel.saveUserChat(userChat);
messageViewModel.userUpdatedSuccessfully.observe(MessageActivity.this, aBoolean -> {
if (aBoolean) {
Util.stopProgressBar();
getChatList();
Toast.makeText(MessageActivity.this, getString(R.string.showMessageSuccess), Toast.LENGTH_SHORT).show();
} else {
Util.stopProgressBar();
Toast.makeText(MessageActivity.this, getString(R.string.showMessageFailed), Toast.LENGTH_SHORT).show();
}
});
break;
case Constants.MESSAGE_TYPE_TEXT:
userChat.setMessage_data(textSend.getText().toString());
messageViewModel.saveUserChat(userChat);
messageViewModel.userUpdatedSuccessfully.observe(MessageActivity.this, aBoolean -> {
if (aBoolean) {
Util.stopProgressBar();
getChatList();
} else {
Util.stopProgressBar();
}
});
break;
}
messageViewModel.queryForToken(receiverPhoneNumber, MessageActivity.this);
}
Send push notification
Java:
PushApis pushApis = new PushApis(MessageActivity.this);
if (messageType.equalsIgnoreCase(Constants.MESSAGE_TYPE_MAP)) {
pushApis.sendPushNotification(roomId, messageType, "104739093", jsonMapModel, s);
} else if (messageType.equalsIgnoreCase(Constants.MESSAGE_TYPE_TEXT)) {
pushApis.sendPushNotification(roomId, messageType, "104739093", MessageActivity.this.textSend.getText().toString(), s);
textSend.setText("");
}
Conclusion
In this article, we have learned how we can create a simple messaging application with Cloud DB, Auth Service, Push Kit , Location Kit , Site Kit and Map Kit. We can also use cloud storage to store profile picture, location, documents or audio and video files.
Thanks for reading this article. Be sure to like and comment to this article, if you found it helpful. It means a lot to me.
Reference
https://developer.huawei.com/consumer/en/agconnect/cloud-base/
https://developer.huawei.com/consum...-Guides/service-introduction-0000001050040060
https://developer.huawei.com/consumer/en/hms/huawei-locationkit/
https://developer.huawei.com/consumer/en/hms/huawei-MapKit/
https://developer.huawei.com/consumer/en/hms/huawei-sitekit/

Integration of Huawei Account Kit and Analytics Kit in Quiz Android app (Kotlin) – Part 1

{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Introduction
In this article, we can learn how to integrate the Huawei Account Kit and Analytics Kit in Quiz Application. The quiz application is mainly intended to develop the knowledge for users and also to learn about particular topics. So, I will provide the series of articles on this Quiz App, in upcoming articles I will integrate other Huawei Kits.
Account Kit
Huawei Account Kit provides for developers with simple, secure, and quick sign-in and authorization functions. User is not required to enter accounts, passwords and waiting for authorization. User can click on Sign In with HUAWEI ID button to quickly and securely sign in to the app.
Analytics Kit
HUAWEI Analytics Kit provides analysis models to understand user behaviour and gain in-depth insights into users, products and content. It helps you to gain insight about user behaviour on different platforms based on the user behaviour events and user attributes reported by through apps.
AppGallery Connect
Find the Analytics using AppGallery connect dashboard.
Choose My Projects > Huawei Analytics > Overview > Project overview.
Project overview displays the core indicators of current project, such as the number of new users, User activity, User acquisition, User revisit, New user retention, Active user retention, User characteristics and Popular pages etc. providing a quick overview of the users and how they are using your app.
Requirements
1. Any operating system (MacOS, Linux and Windows).
2. Must have a Huawei phone with HMS 4.0.0.300 or later.
3. Must have a laptop or desktop with Android Studio, Jdk 1.8, SDK platform 26 and Gradle 4.6 and above installed.
4. Minimum API Level 24 is required.
5. Required EMUI 9.0.0 and later version devices.
How to integrate HMS Dependencies
1. First register as Huawei developer and complete identity verification in Huawei developers website, refer to register a Huawei ID.
2. Create a project in android studio, refer Creating an Android Studio Project.
3. Generate a SHA-256 certificate fingerprint.
4. To generate SHA-256 certificate fingerprint. On right-upper corner of android project click Gradle, choose Project Name > Tasks > android, and then click signingReport, as follows.
Note: Project Name depends on the user created name.
5. Create an App in AppGallery Connect.
6. Download the agconnect-services.json file from App information, copy and paste in android Project under app directory, as follows.
7. Enter SHA-256 certificate fingerprint and click Save button, as follows.
Note: Above steps from Step 1 to 7 is common for all Huawei Kits.
8. Click Manage APIs tab and enable Account Kit and HUAWEI Analytics.
9. Add the below maven URL in build.gradle(Project) file under the repositories of buildscript, dependencies and allprojects, refer Add Configuration.
Java:
maven { url 'http://developer.huawei.com/repo/' }
classpath 'com.huawei.agconnect:agcp:1.6.0.300'
10. Add the below plugin and dependencies in build.gradle(Module) file.
Java:
apply plugin: id 'com.huawei.agconnect'
// Huawei AGC
implementation 'com.huawei.agconnect:agconnect-core:1.6.0.300'
// Huawei Account Kit
implementation 'com.huawei.hms:hwid:6.3.0.301'
// Huawei Analytics Kit
implementation 'com.huawei.hms:hianalytics:6.4.0.300'
11. Now Sync the gradle.
12. Add the required permission to the AndroidManifest.xml file.
XML:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
Let us move to development
I have created a project on Android studio with empty activity let us start coding.
In the MainActivity.kt we can find the business logic for Huawei login button.
Java:
class MainActivity : AppCompatActivity() {
// Account Kit variables
private var mAuthManager: AccountAuthService? = null
private var mAuthParam: AccountAuthParams? = null
// Analytics Kit - Initialize the Analytics
var mInstance: HiAnalyticsInstance? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Account kit button click Listener
btn_login.setOnClickListener(mOnClickListener)
// Initialize the Analytics function
initAna()
}
// Account kit, method to send an authorization request.
private fun signIn() {
mAuthParam = AccountAuthParamsHelper(AccountAuthParams.DEFAULT_AUTH_REQUEST_PARAM)
.setIdToken()
.setAccessToken()
.setProfile()
.createParams()
mAuthManager = AccountAuthManager.getService([email protected], mAuthParam)
startActivityForResult(mAuthManager?.signInIntent, 1002)
}
private val mOnClickListener: View.OnClickListener = object : View.OnClickListener {
override fun onClick(v: View?) {
when (v?.id) {
R.id.btn_login -> signIn()
}
}
}
// Process the authorization result
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 1002 ) {
val authAccountTask = AccountAuthManager.parseAuthResultFromIntent(data)
if (authAccountTask.isSuccessful) {
// Analytics data to send custom events
val bundle = Bundle()
bundle.putString("name", data!!.extras!!.getString("name" ))
bundle.putString("email", data!!.extras!!.getString("email" ))
mInstance!!.onEvent(HAEventType.SIGNIN, bundle)
Toast.makeText(this, "SigIn success", Toast.LENGTH_LONG).show()
val intent = Intent([email protected], Home::class.java)
startActivity(intent)
} else {
Toast.makeText(this, "SignIn failed: " + (authAccountTask.exception as ApiException).statusCode, Toast.LENGTH_LONG).show()
}
}
}
private fun initAna() {
// Enable Analytics Kit Log
HiAnalyticsTools.enableLog()
// Generate the Analytics Instance
mInstance = HiAnalytics.getInstance(this)
// Enable collection capability
mInstance?.setAnalyticsEnabled(true)
}
}
In the activity_main.xml we can create the UI screen.
Java:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:ignore="ExtraText,MissingConstraints">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="273dp"
android:background="@drawable/blue_bg">
<ImageView
android:layout_width="77dp"
android:layout_height="77dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="93dp"
android:src="@drawable/quiz_icon" />
</FrameLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="62dp"
android:layout_marginTop="37dp"
android:layout_marginRight="62dp"
android:background="@drawable/blue_border_rounded_cornwe">
<EditText
android:id="@+id/edt_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/username_icon"
android:background="@android:color/transparent"
android:hint="Email"
android:inputType="textEmailAddress"
android:maxLines="1"
android:paddingLeft="17dp"
android:paddingTop="15dp"
android:paddingBottom="15dp"
android:textSize="13sp">
</EditText>
<ImageView
android:id="@+id/username_icon"
android:layout_width="18dp"
android:layout_height="13dp"
android:layout_centerVertical="true"
android:layout_marginLeft="17dp"
android:src="@drawable/email" />
</RelativeLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="62dp"
android:layout_marginTop="13dp"
android:layout_marginRight="62dp"
android:background="@drawable/blue_border_rounded_cornwe">
<EditText
android:id="@+id/edt_pass"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/pass_icon"
android:background="@android:color/transparent"
android:hint="Password"
android:inputType="textPassword"
android:maxLength="10"
android:maxLines="1"
android:paddingLeft="17dp"
android:paddingTop="15dp"
android:paddingBottom="15dp"
android:textSize="13sp">
</EditText>
<ImageView
android:id="@+id/pass_icon"
android:layout_width="18dp"
android:layout_height="13dp"
android:layout_centerVertical="true"
android:layout_marginLeft="17dp"
android:src="@drawable/password" />
</RelativeLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="62dp"
android:layout_marginTop="19dp"
android:layout_marginRight="62dp"
android:background="@drawable/blue_fill__rounded_color"
android:gravity="center"
android:paddingTop="14dp"
android:paddingBottom="14dp"
android:text="Login"
android:textColor="@color/white"
android:textSize="13sp">
</TextView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="12dp"
android:paddingTop="14dp"
android:paddingBottom="14dp"
android:text="FORGOT PASSWORD ?"
android:textColor="#1566e0"
android:textSize="12sp">
</TextView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="25dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="14dp"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="or"
android:textColor="#0E0E0E"
android:textSize="15sp">
</TextView>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="25dp"
android:gravity="center"
android:orientation="horizontal">
<ImageView
android:id="@+id/btn_login"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/huawei_icon" />
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:src="@drawable/facebook_icon" />
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginRight="20dp"
android:src="@drawable/instagram_icon" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
Demo
Tips and Tricks
1. Make sure you are already registered as Huawei developer.
2. Set minSDK version to 24 or later, otherwise you will get AndriodManifest merge issue.
3. Make sure you have added the agconnect-services.json file to app folder.
4. Make sure you have added SHA-256 fingerprint without fail.
5. Make sure all the dependencies are added properly.
Conclusion
In this article, we have learned to integrate the Huawei Account Kit and Analytics Kit in Quiz Application. The quiz application is mainly intended to develop the knowledge for users and also to learn about particular topics. So, I will provide a series of articles on this Quiz App, in upcoming articles I will integrate other Huawei Kits.
I hope you have read this article. If you found it helpful, please provide likes and comments.
Reference
Account Kit – Documentation
Account Kit – Training Video
Analytics Kit – Documentation
Analytics Kit – Training Video

Categories

Resources