{
"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"
}
Hello everyone.
In this article, I will talk about the Search Kit, which a new feature offered by Huawei to developers, and how to use it in android applications.
What is Search Kit?
Search Kit is one of Huawei’s latest released features. One of the most liked features of Huawei, which continues to improve itself day by day and offer new features to software developers, was the Search Kit.
Search Kit provides to you quickly and easily use a seamless mobile application search experience within the HMS ecosystem by using Petal Search APIs in the background.
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.
Search Kit provides to developers with 4 different types of searches. These are Web Search, News Search, Image Search and Video Search.
I am sure that Search Kit will attract all developers in a very short time, as it offers a fast application development experience, and its output is consistently and quickly and completely free.
Development Steps
1. Integration
First, a developer account must be created and HMS Core must be integrated into the project to use HMS. You can access the article about that steps from the link below.
2.Adding Dependencies
After HMS Core is integrated into the project and the Search Kit is activated through the console, the required library should added to the build.gradle file in the app directory as follows.
Code:
dependencies {
implementation 'com.huawei.hms:searchkit:5.0.4.303'
}
The project’s minSdkVersion value should be 24. For this, the minSdkVersion value in the same file should be updated to 24.
Code:
android {
...
defaultConfig {
...
minSdkVersion 24
...
}
...
}
3.Adding Permissions
The following line should be added to the AndroidManifest.xml file to allow HTTP requests. Absolutely, we shouldn’t forget to add internet permissions.
Code:
<application
...
android:usesCleartextTraffic="true"
>
...
</application>
4.Create Application Class
An Application class is required to launch the Search Kit when starting the application. Search Kit is launched in this class and it is provided to start with the project. Here, App Id must be given as parameter while initting Search Kit. After the BaseApplication class is created, it must be defined in the Manifest file.
Code:
class BaseApplication: Application() {
override fun onCreate() {
super.onCreate()
SearchKitInstance.init(this, Constants.APP_ID)
}
}
5.Search Screen
After all of the permissions and libraries have been added to the project, search operations can started. For this, a general search screen should be designed first. In its simplest form, adding a search box, 4 buttons to select the search type, and a recyclerView can create a simple and elegant search screen. For reference, I share the screen I created in the below.
Thanks to the design, you can list the 4 different search results type on the same page using different adapters.
Code:
<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=".ui.view.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="10dp"
android:layout_marginRight="10dp">
<EditText
android:id="@+id/searchText"
android:layout_width="match_parent"
android:layout_height="36dp"
android:background="@drawable/search_box"
android:focusable="true"
android:focusableInTouchMode="true"
android:gravity="center_vertical|start"
android:hint="Search"
android:fontFamily="@font/muli_regular"
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/selected_search_box"
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_search" />
</RelativeLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="left"
android:layout_below="@+id/searchview_layout"
android:id="@+id/database_searchButtons"
android:layout_marginTop="25dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:background="@drawable/search_box">
<TextView
android:layout_width="wrap_content"
android:layout_height="36dp"
android:layout_weight="1"
android:gravity="center"
android:layout_marginLeft="20dp"
android:background="#e8e6e5"
android:id="@+id/btn_searchWeb"
android:text="Web"
android:textAllCaps="false"
android:textStyle="bold"
android:textColor="#000000"
android:fontFamily="@font/muli_regular"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="36dp"
android:layout_weight="1"
android:gravity="center"
android:background="#e8e6e5"
android:id="@+id/btn_searchNews"
android:text="News"
android:textAllCaps="false"
android:textStyle="bold"
android:textColor="#000000"
android:fontFamily="@font/muli_regular"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="36dp"
android:layout_weight="1"
android:gravity="center"
android:background="#e8e6e5"
android:id="@+id/btn_searchImage"
android:text="Image"
android:textAllCaps="false"
android:textStyle="bold"
android:textColor="#000000"
android:fontFamily="@font/muli_regular"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="36dp"
android:layout_weight="1"
android:gravity="center"
android:layout_marginRight="30dp"
android:background="#e8e6e5"
android:id="@+id/btn_searchVideo"
android:textAllCaps="false"
android:text="Video"
android:textStyle="bold"
android:textColor="#000000"
android:fontFamily="@font/muli_regular"/>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_below="@+id/database_searchButtons"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="15dp"
android:fontFamily="@font/muli_regular">
</androidx.recyclerview.widget.RecyclerView>
</RelativeLayout>
6.Create List Item and Adapters
A list item must be designed for the search results to listed in RecyclerView. For that, you can design as you wish and simply create adapter classes. For sample, you can see how the results are listed in my project in the next steps.
7.Web Search
To search on the web, a method should be created that taking the search word and access token values as parameters and returns the WebItem values as an array. WebItem is a kind of model class that comes automatically with the Search Kit library. In this way, the WebItem object can be used without the need to define another model class. In this method, firstly, an object must be created from WebSearchRequest() object and some parameters must be set. You can find a description of these values in the below.
Code:
webSearchRequest.setQ() -> Search text.
webSearchRequest.setLang() - > Search language.
webSearchRequest.setSregion() -> Search region.
webSearchRequest.setPs() -> Result number.
webSearchRequest.setPn() -> Page number.
After the values are set and the web search is started, the values can be set to the WebItem object and added to the list with a “for” loop and returned this list.
The Web Search method should be as following. In addition, as can be seen in the code, all values of the WebItem object are printed with the logs.
Code:
fun doWebSearch(searchText: String, accessToken: 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(accessToken)
val webSearchResponse = SearchKitInstance.getInstance().webSearcher.search(webSearchRequest)
for(i in webSearchResponse.getData()){
webResults.add(i)
Log.i(Constants.TAG_SEARCH_REPOSITORY, "site_name : " + i.site_name + "\n"
+ "getSnippet : " + i.getSnippet() + "\n"
+ "siteName : " + i.siteName + "\n"
+ "title : " + i.title + "\n"
+ "clickUrl : " + i.clickUrl + "\n"
+ "click_url : " + i.click_url + "\n"
+ "getTitle : " + i.getTitle())
}
return webResults
}
The results of the doWebSearch() method can be listed by transferring them to RecyclerView with the adapter. Yo can find a sample screenshot in the below.
8.News Search
To search on the news, a method should be created that taking the search word and access token values as parameters and returns the NewsItem values as an array. NewsItem is a kind of model class that comes automatically with the Search Kit library. In this way, the NewsItem object can be used without the need to define another model class. In this method, firstly, an object must be created from CommonSearchRequest() object and some parameters must be set. You can find a description of these values in the below.
Code:
commonSearchRequest.setQ() -> Search text.
commonSearchRequest.setLang() - > Search language.
commonSearchRequest.setSregion() -> Search region.
commonSearchRequest.setPs() -> Result number.
commonSearchRequest.setPn() -> Page number.
After the values are set and the news search is started, the values can be set to the NewsItem object and added to the list with a “for” loop and returned this list.
The News Search method should be as following. In addition, as can be seen in the code, all values of the NewsItem object are printed with the logs.
Code:
fun doNewsSearch(searchText: String, accessToken: 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(accessToken)
val newsSearchResponse = SearchKitInstance.getInstance().newsSearcher.search(commonSearchRequest)
for(i in newsSearchResponse.getData()) {
newsResults.add(i)
Log.i(
Constants.TAG_SEARCH_REPOSITORY,
"provider : " + i.provider + "\n"
+ "sourceImage.imageHostpageUrl : " + i.provider.logo + "\n"
+ "provider.logo : " + i.provider.siteName + "\n"
+ "provider.site_name : " + i.provider.site_name + "\n"
+ "provider.getLogo() : " + i.provider.getLogo() + "\n"
+ "publishTime : " + i.publishTime + "\n"
+ "getProvider() : " + i.getProvider() + "\n"
+ "getProvider().getLogo() : " + i.getProvider().getLogo() + "\n"
+ "getProvider().site_name : " + i.getProvider().site_name + "\n"
+ "getProvider().siteName : " + i.getProvider().siteName
)
Log.i(
Constants.TAG_SEARCH_REPOSITORY,
"getProvider().logo : " + i.getProvider().logo + "\n"
+ "publish_time : " + i.publish_time + "\n"
+ "getThumbnail() : " + i.getThumbnail() + "\n"
+ "click_url : " + i.click_url + "\n"
+ "thumbnail : " + i.thumbnail + "\n"
+ "getTitle(): " + i.getTitle() + "\n"
+ "title : " + i.title
)
}
return newsResults
}
The results of the doNewsSearch() method can be listed by transferring them to RecyclerView with the adapter. Yo can find a sample screenshot in the below.
9.Image Search
To search on the images, a method should be created that taking the search word and access token values as parameters and returns the ImageItem values as an array. ImageItem is a kind of model class that comes automatically with the Search Kit library. In this way, the ImageItem object can be used without the need to define another model class. In this method, firstly, an object must be created from CommonSearchRequest() object and some parameters must be set. You can find a description of these values in the below.
Code:
commonSearchRequest.setQ() -> Search text.
commonSearchRequest.setLang() - > Search language.
commonSearchRequest.setSregion() -> Search region.
commonSearchRequest.setPs() -> Result number.
commonSearchRequest.setPn() -> Page number.
After the values are set and the image search is started, the values can be set to the ImageItem object and added to the list with a “for” loop and returned this list.
The Image Search method should be as following. In addition, as can be seen in the code, all values of the ImageItem object are printed with the logs.
Code:
fun doImageSearch(searchText: String, accessToken: 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(accessToken)
val imageSearchResponse = SearchKitInstance.getInstance().imageSearcher.search(commonSearchRequest)
for(i in imageSearchResponse.getData()) {
imageResults.add(i)
Log.i(
Constants.TAG_SEARCH_REPOSITORY,
"IMAGE sourceImage.imageContentUrl : " + i.sourceImage.imageContentUrl + "\n"
+ "sourceImage.image_content_url : " + i.sourceImage.image_content_url + "\n"
+ "sourceImage.imageHostpageUrl : " + i.sourceImage.imageHostpageUrl + "\n"
+ "sourceImage.image_hostpage_url : " + i.sourceImage.image_hostpage_url + "\n"
+ "sourceImage.height : " + i.sourceImage.height + "\n"
+ "sourceImage.width : " + i.sourceImage.width + "\n"
+ "sourceImage.getHeight() : " + i.sourceImage.getHeight() + "\n"
+ "sourceImage.getWidth() : " + i.sourceImage.getWidth() + "\n"
+ "sourceImage.publishTime : " + i.sourceImage.publishTime + "\n"
+ "sourceImage.publish_time : " + i.sourceImage.publish_time + "\n"
+ "source_image : " + i.source_image + "\n"
+ "sourceImage : " + i.sourceImage
)
Log.i(
Constants.TAG_SEARCH_REPOSITORY, "title : " + i.title + "\n"
+ "getTitle() : " + i.getTitle() + "\n"
+ "thumbnail : " + i.thumbnail + "\n"
+ "click_url : " + i.click_url + "\n"
+ "clickUrl : " + i.clickUrl + "\n"
+ "getThumbnail() : " + i.getThumbnail()
)
}
return imageResults
}
The results of the doImageSearch() method can be listed by transferring them to RecyclerView with the adapter. Yo can find a sample screenshot in the below.
10.Video Search
To search on the videos, a method should be created that taking the search word and access token values as parameters and returns the VideoItem values as an array. VideoItem is a kind of model class that comes automatically with the Search Kit library. In this way, the VideoItem object can be used without the need to define another model class. In this method, firstly, an object must be created from CommonSearchRequest() object and some parameters must be set. You can find a description of these values in the below.
Code:
commonSearchRequest.setQ() -> Search text.
commonSearchRequest.setLang() - > Search language.
commonSearchRequest.setSregion() -> Search region.
commonSearchRequest.setPs() -> Result number.
commonSearchRequest.setPn() -> Page number.
After the values are set and the video search is started, the values can be set to the VideoItem object and added to the list with a “for” loop and returned this list.
The Video Search method should be as following. In addition, as can be seen in the code, all values of the VideoItem object are printed with the logs.
Code:
fun doVideoSearch(searchText: String, accessToken: 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(accessToken)
val videoSearchResponse = SearchKitInstance.getInstance().videoSearcher.search(commonSearchRequest)
for(i in videoSearchResponse.getData()) {
videoResults.add(i)
Log.i(
Constants.TAG_SEARCH_REPOSITORY,
"getDuration() : " + i.getDuration() + "\n"
+ "provider : " + i.provider + "\n"
+ "sourceImage.imageHostpageUrl : " + i.provider.logo + "\n"
+ "provider.logo : " + i.provider.siteName + "\n"
+ "provider.site_name : " + i.provider.site_name + "\n"
+ "provider.getLogo() : " + i.provider.getLogo() + "\n"
+ "duration : " + i.duration + "\n"
+ "publishTime : " + i.publishTime + "\n"
+ "getProvider() : " + i.getProvider() + "\n"
+ "getProvider().getLogo() : " + i.getProvider().getLogo() + "\n"
+ "getProvider().site_name : " + i.getProvider().site_name + "\n"
+ "getProvider().siteName : " + i.getProvider().siteName
)
Log.i(
Constants.TAG_SEARCH_REPOSITORY,
"getProvider().logo : " + i.getProvider().logo + "\n"
+ "publish_time : " + i.publish_time + "\n"
+ "getThumbnail() : " + i.getThumbnail() + "\n"
+ "click_url : " + i.click_url + "\n"
+ "thumbnail : " + i.thumbnail + "\n"
+ "getTitle(): " + i.getTitle() + "\n"
+ "title : " + i.title
)
}
return videoResults
}
The results of the doVideoSearch() method can be listed by transferring them to RecyclerView with the adapter. Yo can find a sample screenshot in the below.
11.Detail Pages
After the search results are transferred to RecyclerView, one page can be designed, directed by the “Detail >>” button to view the details. You can also get help from your adapter class to transfer the result of the item you selected to the page you designed. For an example, you can examine the detail pages I have created in the below. On the detail pages, you can open the relevant links, view the images and videos etc.
Related
Hi guys,
i have written an Android launcher. It is working fine, but I want to show badge numbers on apps on homescreen (new message in WhatsApp, Facebook ....)
Any idea how this is done? I tried this already and it is not working:
Manifest:
Code:
<receiver android:name=".MyBroadcastReceiver" android:exported="true">
<intent-filter>
<action android:name="com.sonyericsson.home.action.UPDATE_BADGE"/>
<action android:name="android.intent.action.BADGE_COUNT_UPDATE"/>
</intent-filter>
</receiver>
BroadCastReceiver:
Code:
public class MyBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "MyBroadcastReceiver";
@Override
public void onReceive(Context context, Intent intent) {
StringBuilder sb = new StringBuilder();
sb.append("Action: " + intent.getAction() + "\n");
sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
sb.append("App: " + intent.getStringExtra("badge_count_package_name") + "\n");
sb.append("Class: " + intent.getStringExtra("badge_count_class_name") + "\n");
sb.append("Count: " + intent.getStringExtra("badge_count") + "\n");
String log = sb.toString();
System.out.println(TAG + log);
}
}
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Huawei Push Kit is a messaging service provided by Huawei for developers. It establishes a communication channel between the cloud and devices. By using Huawei Push Kit, developers can send the latest messages to users. This helps developers maintain closer ties with users and increases user awareness and activity. User can tap the message displayed in the notification bar of the device and can open the corresponding app and view more details. Huawei Push Kit is already available more than 200+ countries and regions. It offers the capacity of sending 10 million messages per second from the server, delivering 99% of them and providing real time push reports ultimately helping improve the DAU of your apps.
Today in this article we are going to see how to integrate HMS core Push kit into your apps.
Prerequisite
1) Must have a Huawei Developer Account.
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 installed.
Things Need To Be Done
1) First we need to create a project in android studio.
2) Get the SHA Key. For getting the SHA key we can refer to this article.
3) Create an app in the Huawei app gallery connect.
4) Enable push kit setting in Manage APIs section.
5) Provide the SHA Key in App Information Section.
6) Provide storage location.
7) Under Develop tab, go to Growing > Push and select service status Enable.
8) After completing all the above points we need to download the agconnect-services.json from App Information Section. Copy and paste the Json file in the app folder of the android project.
9) Copy and paste the below maven url inside the repositories of buildscript and allprojects ( project build.gradle file )
maven { url 'http://developer.huawei.com/repo/' }
10) Copy and paste the below plugin in the app build.gradle file
apply plugin: 'com.huawei.agconnect'
11) Now Sync the gradle.
Let’s Code
Service Class
First we need to configure app AndroidManifest.xml file. Before we go ahead and providing configuration to the AndroidManifest.xml file, we need to create a Push service class which will extend HmsMessageService class.
This service class will provide us with two callback methods i.e. onNewToken() and onMessageReceived(). In onNewToken() method we will receive the token here and onMessageReceived() method we will receive the data messages sent by Huawei Push.
Code:
public class MyPushService extends HmsMessageService {
private static final String TAG = "PushDemoLog";
@Override
public void onNewToken(String token) {
super.onNewToken(token);
Log.i(TAG, "receive token:" + token);
}
@Override
public void onMessageReceived(RemoteMessage message) {
super.onMessageReceived(message);
Log.i(TAG, "getCollapseKey: " + message.getCollapseKey()
+ "\n getData: " + message.getData()
+ "\n getFrom: " + message.getFrom()
+ "\n getTo: " + message.getTo()
+ "\n getMessageId: " + message.getMessageId()
+ "\n getSendTime: " + message.getSentTime()
+ "\n getMessageType: " + message.getMessageType()
+ "\n getTtl: " + message.getTtl());
RemoteMessage.Notification notification = message.getNotification();
if (notification != null) {
Log.i(TAG, "\n getImageUrl: " + notification.getImageUrl()
+ "\n getTitle: " + notification.getTitle()
+ "\n getTitleLocalizationKey: " + notification.getTitleLocalizationKey()
+ "\n getTitleLocalizationArgs: " + Arrays.toString(notification.getTitleLocalizationArgs())
+ "\n getBody: " + notification.getBody()
+ "\n getBodyLocalizationKey: " + notification.getBodyLocalizationKey()
+ "\n getBodyLocalizationArgs: " + Arrays.toString(notification.getBodyLocalizationArgs())
+ "\n getIcon: " + notification.getIcon()
+ "\n getSound: " + notification.getSound()
+ "\n getTag: " + notification.getTag()
+ "\n getColor: " + notification.getColor()
+ "\n getClickAction: " + notification.getClickAction()
+ "\n getChannelId: " + notification.getChannelId()
+ "\n getLink: " + notification.getLink()
+ "\n getNotifyId: " + notification.getNotifyId());
}
}
}
This service need to be mention or configure in the AndroidManifest.xml file. Copy and paste the below code after </activity> tag and before </application> tag.
Code:
<service
android:name=".MyPushService"
android:exported="false">
<intent-filter>
<action android:name="com.huawei.push.action.MESSAGING_EVENT" />
</intent-filter>
</service>
To get the token from the HMS Push kit we need to write the following code in the Activity class (it could be any activity class example MainActivity).
Code:
private void getToken(){
new Thread() {
@Override
public void run() {
try {
String appId = AGConnectServicesConfig.fromContext(MainActivity.this).getString("client/app_id");
pushtoken = HmsInstanceId.getInstance(MainActivity.this).getToken(appId, "HCM");
if(!TextUtils.isEmpty(pushtoken)) {
Log.i(TAG, "get token:" + pushtoken);
showLog(pushtoken);
}
} catch (Exception e) {
Log.i(TAG,"getToken failed, " + e);
}
}
}.start();
}
On the log we will be able to see the token as shown below
Turn Off / On Notification
Suppose user doesn’t want any notification from your app, we can achieve this functionality by simply calling HmsMessaging class. This class contain two methods turnOnPush() and turnOffPush(). Below you will find the code to turn off / on notification. By default the notification message is enable.
Code:
private void turnOnNotification(){
HmsMessaging.getInstance(this).turnOnPush().addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(Task<Void> task) {
if (task.isSuccessful()) {
Log.i(TAG, "turnOnPush Complete");
} else {
Log.e(TAG, "turnOnPush failed: ret=" + task.getException().getMessage());
}
}
});
}
private void turnOffNotification(){
HmsMessaging.getInstance(this).turnOffPush().addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(Task<Void> task) {
if (task.isSuccessful()) {
Log.i(TAG, "turnOnPush Complete");
} else {
Log.e(TAG, "turnOnPush failed: ret=" + task.getException().getMessage());
}
}
});
}
Sending Notification
To send notification message we need to go to AGC and select My apps. Under My apps we will find the app which we have created to get notification. Select the app go to Operate > Promotion > Push. Select Add Notification button. Provide details as shown below and click Test effect button which will ask for token. Put the token and select okay.
We have a second part of this article i.e. HMS PUSH KIT SERVER SIDE ( PART 2 ). I would recommend you to go for it. It will help you to have a clear picture of HMS Push Kit.
That’s it
For more information like this, you can visit https://forums.developer.huawei.com/forumPortal/en/home
Introduction
Flight booking app allows user to search and book flight. In this article, we will integrate app messaging and analytics into demo flight booking application.
For prerequisite, permission and set up, refer to part 1.
Usecase
1. We will integrate Huawei Crash analytics to monitors and captures your crashes, also it analyzes them, and then groups into manageable issues. And it does this through lightweight SDK that won’t bloat your app. You can integrate Huawei crash analytics SDK with a single line of code before you publish.
2. We will send push notification which establishes communication between cloud and devices. Using HMS push kit, developers can send message to user.
Crash Analytics
1. In order to use Crash service, we need to integrate Analytics kit by adding following code in app-level build.gradle.
Code:
implementation 'com.huawei.hms:hianalytics:5.0.1.301'
2. Enable Analytics in ACG. Refer Service Enabling.
3. Integrate Crash SDK by adding following code in app-level build.gradle.
Code:
implementation 'com.huawei.agconnect:agconnect-crash:1.4.1.300'
4. In onCreate() of MainActivity class, enable crash analytics.
Code:
AGConnectCrash.getInstance().enableCrashCollection(true);
5. To trigger a crash
Code:
AGConnectCrash.getInstance().testIt(this);
Note: Please comment the above code before releasing or publishing the app. It deliberately causes app to crash and just used for testing HMS crash service.
6. To monitor a crash report inApp Gallery Connect, select your app from MyProjects. Select Quality -> Crash on left panel of the screen. Click on Problems.
{
"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"
}
7. Setting screen has been designed to enable crash service.
Push Kit
1. Enable push kit in AGC. Refer Service Enabling.
2. Integrate Push kit SDK by adding following code in app-level build.gradle.
Code:
implementation 'com.huawei.hms:push:5.0.1.300'
3. Call getToken() method in onCreate() of MainActivity class. Generated token will be used later in AGC to send notification.
Code:
private void getToken() {
new Thread() {
@Override
public void run() {
try {
// read from agconnect-services.json
String appId = AGConnectServicesConfig.fromContext(MainActivity.this).getString("client/app_id");
String token = HmsInstanceId.getInstance(MainActivity.this).getToken(appId, "HCM");
Log.i(TAG, "get token:" + token);
if(!TextUtils.isEmpty(token)) {
sendRegTokenToServer(token);
}
} catch (ApiException e) {
Log.e(TAG, "get token failed, " + e);
}
}
}.start();
}
private void sendRegTokenToServer(String token) {
Log.i(TAG, "sending token to server. token:" + token);
}
4. To deregister the token.
Code:
new Thread() {
@Override
public void run() {
try {
// read from agconnect-services.json
String appId = AGConnectServicesConfig.fromContext(this).getString("client/app_id");
HmsInstanceId.getInstance(this).deleteToken(appId, "HCM");
Log.i(TAG, "deleteToken success.");
} catch (ApiException e) {
Log.e(TAG, "deleteToken failed." + e);
}
}
}.start();
5. Create a Service which extends HmsMessageService class. Declare it in Manifest.
Code:
<service
android:name=".service.FLightHmsMessageService"
android:exported="false">
<intent-filter>
<action android:name="com.huawei.push.action.MESSAGING_EVENT"/>
</intent-filter>
</service>
6. Override onMessageReceivedMethod() to obtain the message data.
Code:
@Override
public void onMessageReceived(RemoteMessage message) {
super.onMessageReceived(message);
Log.i(TAG, "getCollapseKey: " + message.getCollapseKey()
+ "\n getData: " + message.getData()
+ "\n getFrom: " + message.getFrom()
+ "\n getTo: " + message.getTo()
+ "\n getMessageId: " + message.getMessageId()
+ "\n getSendTime: " + message.getSentTime()
+ "\n getMessageType: " + message.getMessageType()
+ "\n getTtl: " + message.getTtl());
RemoteMessage.Notification notification = message.getNotification();
if (notification != null) {
Log.i(TAG, "\n getImageUrl: " + notification.getImageUrl()
+ "\n getTitle: " + notification.getTitle()
+ "\n getTitleLocalizationKey: " + notification.getTitleLocalizationKey()
+ "\n getTitleLocalizationArgs: " + Arrays.toString(notification.getTitleLocalizationArgs())
+ "\n getBody: " + notification.getBody()
+ "\n getBodyLocalizationKey: " + notification.getBodyLocalizationKey()
+ "\n getBodyLocalizationArgs: " + Arrays.toString(notification.getBodyLocalizationArgs())
+ "\n getIcon: " + notification.getIcon()
+ "\n getSound: " + notification.getSound()
+ "\n getTag: " + notification.getTag()
+ "\n getColor: " + notification.getColor()
+ "\n getClickAction: " + notification.getClickAction()
+ "\n getChannelId: " + notification.getChannelId()
+ "\n getLink: " + notification.getLink()
+ "\n getNotifyId: " + notification.getNotifyId());
}
}
7. Log in to AGC, select your project and navigate to Growing -> Push kit. And compose the message for notification. Provide the token which was earlier generated from getToken() method.
Image
Conclusion:
In this article, we learnt how to integrate Huawei crash service and HMS push kit to in application.
For detailed guide, refer to
https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/service-introduction-0000001050040060
https://developer.huawei.com/consumer/en/doc/development/AppGallery-connect-Guides/agc-crash-introduction
Displaying the Cellular (LTE) Connection Values Obtained by HMS Wireless Kit and the Location Information Obtained Using the Location Kit on the Map Kit
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Hello everyone,
In this article, I will explain what the cellular data values obtained using the Wireless Kit are, how they are used and how the Wireless Kit values obtained based on location can be displayed on the map using the Huawei Map kit and Location kit. Let’s start with the use of the Wireless Kit first.
Wireless Kit Integration and Use of Wireless Kit Data
First we are creating our Application Gallery Connect website project and integrating the HMS core into our application. You can learn the detailed information required for these procedures from the link.
https://developer.huawei.com/consumer/en/hms/huawei-wirelesskit/
Then we create the buttons of the application, which we will do in three stages in MainActivity as below.
We define the buttons we have created in the MainActivity class as below.
Java:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "<!!!> MainActivity";
private TextView resultTextView;
private int textCounter = 0;
private NetworkQoeService networkQoeService;
private LocationService locationService;
private String networkData = null, qoeLevel = null;
private Location locationData = null;
private AlertDialog progressDialog;
@Override
protected void onDestroy() {
super.onDestroy();
networkQoeService.DestroyService();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
networkQoeService = new NetworkQoeService(this);
locationService = new LocationService(this);
resultTextView = findViewById(R.id.resultext);
resultTextView.setMovementMethod(new ScrollingMovementMethod());
Button callbackButton = findViewById(R.id.callbackbtn);
Button realTimeDataButton = findViewById(R.id.getrealtimebtn);
Button openMapButton = findViewById(R.id.openMapButton);
callbackButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getCallBackData();
}
});
realTimeDataButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getNetworkData();
}
});
openMapButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
progressDialog = showProgressAlertDialog();
getNetworkData();
getLocationData();
}
});
}
We enter the necessary permissions as in the manifest file below.
Code:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.huawei.wirelesskit_codelab">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="com.huawei.permission.SECURITY_DIAGNOSE"/>
<application
android:name=".wirelesskit.MapKitApplication"
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/Theme.WirelessKitCodelab">
<activity android:name=".wirelesskit.activity.PermissionActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".wirelesskit.activity.MainActivity"
android:screenOrientation="portrait"/>
<activity android:name=".wirelesskit.activity.MapActivity"/>
<activity android:name=".wirelesskit.video.PlayActivity"
android:theme="@style/Theme.AppCompat.NoActionBar"
android:screenOrientation="landscape"/>
</application>
</manifest>
When we press the callbackButton, which is the first button we defined above, we call the getCallBackData method below and obtain your cellular (LTE) connection’s identifier, uplink latency, downlink latency, uplink bandwidth, downlink bandwidth, uplink rate, downlink rate, QoE level and uplink packet loss rate data respectively.
getCallBackData method can be viewed from the link below.
Java:
private void getCallBackData() {
Bundle data = NetworkQoeService.qoeInfo;
if(data != null)
{
int channelNum = 0;
if (data.containsKey("channelNum")) {
channelNum = data.getInt("channelNum");
}
String channelQoe = "channelNum: " + channelNum;
for (int i = 0; i < channelNum; i++) {
// channelQoe can be displayed on the user interface through EditText.
channelQoe += ",channelIndex: " + data.getInt("channelIndex" + i) + ",uLRtt: " + data.getInt("uLRtt" + i) + ",dLRtt: "
+ data.getInt("dLRtt" + i) + ",uLBandwidth: " + data.getInt("uLBandwidth" + i) + ",dLBandwidth: "
+ data.getInt("dLBandwidth" + i) + ",uLRate: " + data.getInt("uLRate" + i) + ",dLRate: "
+ data.getInt("dLRate" + i) + ",netQoeLevel: " + data.getInt("netQoeLevel" + i) + ",uLPkgLossRate: "
+ data.getInt("uLPkgLossRate" + i)+"\n";
}
updateText(channelQoe);
}else
{
Log.e(TAG, "callbackData else");
}
}
You can also look at the link below for detail information about the getCallBackData() method results.
https://developer.huawei.com/consum...eferences-V5/iqoecallback-0000001050986863-V5
Secondly, we call the getNetworkData() method using the realTimeDataButton button and get real-time NetworkQoe information of the application. In other words, we obtain the last second connection information.
We do this process using the networkQoeService.GetRealTimeData () command as in the code section below.
Java:
private void getNetworkData() {
Bundle data = networkQoeService.GetRealTimeData();
if(data != null) {
int channelNum = 0;
if (data.containsKey("channelNum")) {
channelNum = data.getInt("channelNum");
}
int index = channelNum - 1;
networkData = "Channel Number : " + channelNum + "\n" +
"Channel Index : " + data.getInt("channelIndex" + index) + "\n" +
"UpLink Latency : " + data.getInt("uLRtt" + index) + "\n" +
"DownLink Latency : " + data.getInt("dLRtt" + index) + "\n" +
"UpLink Bandwidth : " + data.getInt("uLBandwidth" + index) + "\n" +
"DownLink Bandwidth : " + data.getInt("dLBandwidth" + index) + "\n" +
"UpLink Rate : " + data.getInt("uLRate" + index) + "\n" +
"DownLink Rate : " + data.getInt("dLRate" + index) + "\n" +
"QoE Level : " + data.getInt("netQoeLevel" + index) + "\n" +
"UpLink Package Loss Rate : " + data.getInt("uLPkgLossRate" + index) + "\n" +
"Download Speed in Mbps : " + data.getInt("dLBandwidth" + index) / 1000.0
;
qoeLevel = data.getInt("netQoeLevel" + index) + "";
updateText(networkData);
}
else {
Log.e(TAG, "realTimeData else");
}
}
Integration of Location and Map Kit and Displaying the Data Obtained on the Screen
We call the showProgressAlertDialog(), getNetworkData(), getLocationData() methods using the openMapButton button we created in our manifest. First, we use the showProgressAlertDialog() method to print the “loading” screen to indicate to the user that the screen should be waited until the other operations to be performed and the location and cellular connection information provided on the map are completed. We do this process using the code section below.
Java:
private AlertDialog showProgressAlertDialog(){
AlertDialog.Builder builder;
builder = new AlertDialog.Builder(this);
builder.setTitle("Loading...");
LayoutInflater factory = LayoutInflater.from(this);
View progressBar = factory.inflate(R.layout.progress_bar, null);
builder.setView(progressBar);
AlertDialog alertDialog = builder.create();
alertDialog.setCancelable(false);
alertDialog.show();
return alertDialog;
}
Secondly, we get real-time NetworkQoe information of the application by calling the getNetworkData() method. We will use the netQoeLevel data in the Wireless Kit results. The netQoeLevel variable takes one of the 4 and 5 values as in the pictures below, according to the connection quality. If the user has a smooth network performance, this value will return 4. However, if the user has poor network performance, this value will return 5.
Finally, we get the location information of the user calling the getLocationData() method, when the network connection quality is calculated. We do the process of obtaining location information as below.
Java:
private void getLocationData() {
locationService.getLocationUpdate(new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
if (locationResult != null) {
locationData = locationResult.getLastLocation();
openMapActivity();
locationService.stopLocationUpdates(this);
progressDialog.cancel();
}
}
});
}
As you see in the code section above, we use the getLocationUpdate method to get location information. Then we get the last location information using getLastLocation() method and we start our map operations using the openMapActivity() method. When we start this process, we stop the location service update process using stopLocationUpdates() because we have obtain the necessary location data. In addition, since the map opening process will start, we use progressDialog.cancel () to close the loading screen.
In the openMapActivity () method, we open the MapActivity using the necessary network information and location information as in the code section below.
Java:
private void openMapActivity() {
this.startActivity(MapActivity.newIntent(this, networkData, qoeLevel, locationData.getLatitude(), locationData.getLongitude()));
}
In the MapActivity’s onCreate() method we are directed here, we make the necessary assignments to the parameters, the width and length of our map, and the necessary definitions for the mapView.
Java:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_map);
networkData = getIntent().getStringExtra(KEY_NETWORK_DATA);
qoeLevel = getIntent().getStringExtra(KEY_QOE_LEVEL);
double latitude = getIntent().getDoubleExtra(KEY_LATITUDE, 0);
double longitude = getIntent().getDoubleExtra(KEY_LONGITUDE, 0);
latLng = new LatLng(latitude, longitude);
mMapView = findViewById(R.id.mapView);
Bundle mapViewBundle = null;
if (savedInstanceState != null) {
mapViewBundle = savedInstanceState.getBundle(MAP_VIEW_BUNDLE_KEY);
}
MapsInitializer.setApiKey(API_KEY);
mMapView.onCreate(mapViewBundle);
mMapView.getMapAsync(this);
}
After completing the operations above, we will work on the onMapReady() method. In this method, we do our map configurations in the section we specified with the update camera view. Then we set the marker in If condition, which will show the user’s location on the map according to the qoeLevel value in the network data we receive from the Wireless Kit. As we explained above in our article, the netQoeLevel parameter will return 4 if the user has a smooth network connection, and in this case, we set the marker showing the user’s location to green. But if the user has poor network performance, this value will return as 5, and in this case, we set the location marker on the map to red to indicate the weak link. Then, when we click on these markers, we define InfoWindowAdapter on HuaweiMap to display network information on the screen. After these processes are finished, we create a LinearLayout to place the network connection data we receive from the Wireless Kit. Finally, we add the titles of the markers that indicate the network quality status that we have determined according to the netQoeLevel parameter to the LinearLayout we have created before, after adjusting the properties such as title text, background like below.
Java:
public void onMapReady(HuaweiMap huaweiMap) {
//update camera view
CameraUpdateParam cameraUpdateParam = new CameraUpdateParam();
cameraUpdateParam.setLatLng(latLng);
CameraUpdate cameraUpdate = new CameraUpdate(cameraUpdateParam);
huaweiMap.moveCamera(cameraUpdate);
//add marker
MarkerOptions markerOptions = new MarkerOptions()
.position(latLng)
.snippet(networkData);
if (GOOD_QOE_LEVEL.equals(qoeLevel)){
markerOptions.title("GOOD NETWORK CONNECTION");
markerOptions.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN));
}
else if (BAD_QOE_LEVEL.equals(qoeLevel)) {
markerOptions.title("BAD NETWORK CONNECTION");
markerOptions.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED));
}
huaweiMap.addMarker(markerOptions);
huaweiMap.setInfoWindowAdapter(new HuaweiMap.InfoWindowAdapter() {
@Override
public View getInfoContents(Marker marker) {
return null;
}
@Override
public View getInfoWindow(Marker marker) {
LinearLayout info = new LinearLayout(MapActivity.this);
info.setOrientation(LinearLayout.VERTICAL);
TextView title = new TextView(MapActivity.this);
title.setTextColor(Color.BLACK);
title.setBackgroundColor(Color.LTGRAY);
title.setTypeface(null, Typeface.BOLD);
title.setText(marker.getTitle());
TextView snippet = new TextView(MapActivity.this);
snippet.setTextColor(Color.BLACK);
snippet.setBackgroundColor(Color.LTGRAY);
snippet.setText(marker.getSnippet());
info.addView(title);
info.addView(snippet);
return info;
}
});
}
You can see the screenshots we created below. Thus, we complete our application.
You can visit the link below for detailed information about the Wireless Kit.
https://developer.huawei.com/consumer/en/hms/huawei-wirelesskit/
You can visit the link below for detailed information about the Map Kit.
https://developer.huawei.com/consumer/en/hms/huawei-MapKit/
You can visit the link below for detailed information about the Location Kit.
https://developer.huawei.com/consumer/en/hms/huawei-locationkit/
Thank you for taking your time and reading my article. Hope to see you in my next forum xda article.
{
"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
Hi everyone, In this article, we’ll explore how to develop a download manager app using the Huawei Network Kit. And, we’ll use Kotlin as a programming language in Android Studio.
Huawei Network Kit
Network Kit provides us to upload or download files with additional features such as multithreaded, concurrent, resumable uploads and downloads. Also, it allows us to perform our network operations quickly and safely. It provides a powerful interacting with Rest APIs and sending synchronous and asynchronous network requests with annotated parameters. Finally, we can use it with other Huawei kits such as hQUIC Kit and Wireless Kit to get faster network traffic.
If you want to learn how to use Network Kit with Rest APIs, you can check my article about it.
Download Manager — Sample App
In this project, we’re going to develop a download manager app that helps users download files quickly and reliably to their devices.
Key features:
Start, Pause, Resume or Cancel downloads.
Enable or Disable Sliced Download.
Set’s the speed limit for downloading a file.
Calculate downloaded size/total file size.
Calculate and display download speed.
Check the progress in the download bar.
Support HTTP and HTTPS protocols.
Copy URL from clipboard easily
We started a download task. Then, we paused and resumed it. When the download is finished, it showed a snackbar to notify us.
Setup the Project
We’re not going to go into the details of integrating Huawei HMS Core into a project. You can follow the instructions to integrate HMS Core into your project via official docs or codelab. After integrating HMS Core, let’s add the necessary dependencies.
Add the necessary dependencies to build.gradle (app level).
Java:
dependencies {
...
// HMS Network Kit
implementation 'com.huawei.hms:filemanager:5.0.3.300'
// For runtime permission
implementation 'androidx.activity:activity-ktx:1.2.3'
implementation 'androidx.fragment:fragment-ktx:1.3.4'
...
}
Let’s add the necessary permissions to our manifest.
XML:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.huawei.networkkitsample">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
...
</manifest>
We added the Internet Permission to access the Internet and the storage permissions to read and write data to the device memory. Also, we will dynamically request the permissions at runtime for storage permissions on devices that runs Android 6.0 (API Level 23) or higher.
Configure the AndroidManifest file to use clear text traffic
If you try to download a file from an HTTP URL on Android 9.0 (API level 28) or higher, you’ll get an error like this:
Code:
ErrorCodeFromException errorcode from resclient: 10000802,message:CLEARTEXT communication to ipv4.download.thinkbroadband.com(your url) not permitted by network security policy
Because cleartext support is disabled by default on Android 9.0 or higher. You should add the android:usesClearTextTraffic="true" flag in the AndroidManifest.xml file. If you don’t want to enable it for all URLs, you can create a network security config file. If you are only working with HTTPS files, you don’t need to add this flag.
XML:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.huawei.networkkitsample">
...
<application
...
android:usesCleartextTraffic="true"
...
</application>
</manifest>
Layout File
activity_main.xml is the only layout file in our project. There are:
A TextInputEditText to enter URL,
Four buttons to control the download process,
A button to paste URL to the TextInputEditText,
A progress bar to show download status,
A seekbar to adjust download speed limit,
A checkbox to enable or disable the “Slide Download” feature,
TextViews to show various information.
activity_main.xml
XML:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_constraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainActivity">
<Button
android:id="@+id/startDownload_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="Start"
app:layout_constraintEnd_toStartOf="@+id/pauseDownload_button"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/enableSliced_checkBox" />
<ProgressBar
android:id="@+id/downloadProgress_progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:progressBackgroundTint="@color/design_default_color_primary_variant"
android:progressTint="@color/design_default_color_primary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/percentProgress_textView" />
<TextView
android:id="@+id/percentProgress_textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="0%"
app:layout_constraintStart_toStartOf="@+id/downloadProgress_progressBar"
app:layout_constraintTop_toBottomOf="@+id/textInputLayout" />
<TextView
android:id="@+id/finishedSize_textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="0"
app:layout_constraintBottom_toTopOf="@+id/downloadProgress_progressBar"
app:layout_constraintStart_toEndOf="@+id/percentProgress_textView"
tools:text="2.5" />
<TextView
android:id="@+id/sizeSeparator_textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="/"
app:layout_constraintBottom_toTopOf="@+id/downloadProgress_progressBar"
app:layout_constraintStart_toEndOf="@+id/finishedSize_textView" />
<TextView
android:id="@+id/totalSize_textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="0"
app:layout_constraintBottom_toTopOf="@+id/downloadProgress_progressBar"
app:layout_constraintStart_toEndOf="@+id/sizeSeparator_textView"
tools:text="29.6 MB" />
<SeekBar
android:id="@+id/speedLimit_seekBar"
style="@style/Widget.AppCompat.SeekBar.Discrete"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:max="7"
android:progress="7"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fixSpeedLimit_textView" />
<TextView
android:id="@+id/fixSpeedLimit_textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="32dp"
android:text="Download Speed Limit:"
app:layout_constraintStart_toStartOf="@+id/speedLimit_seekBar"
app:layout_constraintTop_toBottomOf="@+id/remainingTime_textView" />
<TextView
android:id="@+id/speedLimit_textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="Limitless"
app:layout_constraintBottom_toBottomOf="@+id/fixSpeedLimit_textView"
app:layout_constraintStart_toEndOf="@+id/fixSpeedLimit_textView" />
<TextView
android:id="@+id/currentSpeed_textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0 kB/s"
app:layout_constraintBottom_toTopOf="@+id/downloadProgress_progressBar"
app:layout_constraintEnd_toEndOf="@+id/downloadProgress_progressBar"
tools:text="912 kB/s" />
<Button
android:id="@+id/pauseDownload_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pause"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/startDownload_button"
app:layout_constraintTop_toTopOf="@+id/startDownload_button" />
<Button
android:id="@+id/resumeDownload_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="Resume"
app:layout_constraintEnd_toEndOf="@+id/startDownload_button"
app:layout_constraintStart_toStartOf="@+id/startDownload_button"
app:layout_constraintTop_toBottomOf="@+id/startDownload_button" />
<Button
android:id="@+id/cancelDownload_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="Cancel"
app:layout_constraintEnd_toEndOf="@+id/pauseDownload_button"
app:layout_constraintStart_toStartOf="@+id/pauseDownload_button"
app:layout_constraintTop_toBottomOf="@+id/pauseDownload_button" />
<TextView
android:id="@+id/remainingTime_textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0s left"
app:layout_constraintStart_toStartOf="@+id/downloadProgress_progressBar"
app:layout_constraintTop_toBottomOf="@+id/downloadProgress_progressBar" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toStartOf="@+id/pasteClipboard_imageButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/url_textInputEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="URL"
android:inputType="textUri" />
</com.google.android.material.textfield.TextInputLayout>
<ImageButton
android:id="@+id/pasteClipboard_imageButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginEnd="16dp"
android:background="@android:color/transparent"
android:scaleType="fitXY"
app:layout_constraintBottom_toBottomOf="@+id/textInputLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/textInputLayout"
app:srcCompat="@drawable/ic_paste_content" />
<CheckBox
android:id="@+id/enableSliced_checkBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:checked="true"
android:text="Enable Slice Download"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/speedLimit_seekBar" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity
Let’s interpret some of the functions on this page.
onCreate() - Firstly we used viewBinding instead of findViewById. It generates a binding class for each XML layout file present in that module. With the instance of a binding class, we can access the view hierarchy with type and null safety.
Then, we initialized the ButtonClickListeners and the ViewChangeListeners. And we create a FileRequestCallback object. We’ll go into the details of this object later.
startDownloadButton() - When the user presses the start download button, it requests permissions at runtime. If the user allows accessing device memory, it will start the download process.
startDownload() - First, we check the downloadManager is initialized or not. Then, we check if there is a download task or not. getRequestStatus function provides us the result status as INIT, PROCESS, PAUSE and, INVALID.
Code:
If auto-import is active in your Android Studio, It can import the wrong package for the Result Status. Please make sure to import the "com.huawei.hms.network.file.api.Result" package.
The Builder helps us to create a DownloadManager object. We give a name to our task. If you plan to use the multiple download feature, please be careful to give different names to your download managers.
The DownloadManagerBuilder helps us to create a DownloadManager object. We give a tag to our task. In our app, we only allow single downloading to make it simple. If you plan to use the multiple download feature, please be careful to give different tags to your download managers.
When creating a download request, we need a file path to save our file and a URL to download. Also, we can set a speed limit or enable the slice download.
Code:
Currently, you can only set the speed limit for downloading a file. The speed limit value ranges from 1 B/s to 1 GB/s. speedLimit() takes a variable of the type INT as a byte value.
You can enable or disable the sliced download.
Code:
Sliced Download: It slices the file into multiple small chunks and downloads them in parallel.
Finally, we start an asynchronous request with downloadManager.start() command. It takes the getRequest and the fileRequestCallback.
FileRequestCallback object contains four callback methods: onStart, onProgress, onSuccess and onException.
onStart -> It will be called when the file download starts. We take the startTime to calculate the remaining download time here.
onProgress -> It will be called when the file download progress changes. We can change the progress status here.
Code:
These methods run asynchronously. If we want to update the UI, we should change our thread to the UI thread using the runOnUiThread methods.
onSuccess -> It will be called when file download is completed. We show a snackbar to the user after the file download completes here.
onException -> It will be called when an exception occurs.
Code:
onException also is triggered when the download is paused or resumed. If the exception message contains the "10042002" number, it is paused, if it contains the "10042003", it is canceled.
MainActivity.kt
Java:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var downloadManager: DownloadManager
private lateinit var getRequest: GetRequest
private lateinit var fileRequestCallback: FileRequestCallback
private val TAG = "MainActivity"
private var downloadURL = "http://ipv4.download.thinkbroadband.com/20MB.zip"
private var downloadSpeedLimit: Int = 0
private var startTime: Long = 0L
private var isEnableSlicedDownload = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.urlTextInputEditText.setText(downloadURL)
initButtonClickListeners()
initViewChangeListeners()
fileRequestCallback = object : FileRequestCallback() {
override fun onStart(getRequest: GetRequest): GetRequest {
startTime = System.nanoTime()
return getRequest
}
override fun onProgress(getRequest: GetRequest, progress: Progress) {
runOnUiThread {
binding.downloadProgressProgressBar.progress = progress.progress
binding.percentProgressTextView.text = "${progress.progress}%"
convertByteToMb(progress.totalSize)?.let {
binding.totalSizeTextView.text = "$it MB"
}
convertByteToMb(progress.finishedSize)?.let {
binding.finishedSizeTextView.text = it
}
showCurrentDownloadSpeed(progress.speed)
showRemainingTime(progress)
}
}
override fun onSuccess(response: Response<GetRequest, File, Closeable>?) {
if (response?.content != null) {
runOnUiThread {
binding.downloadProgressProgressBar.progress = 100
binding.percentProgressTextView.text = "100%"
binding.remainingTimeTextView.text = "0s left"
convertByteToMb(response.content.length())?.let {
binding.finishedSizeTextView.text = it
binding.totalSizeTextView.text = "$it MB"
}
showSnackBar(binding.mainConstraintLayout, "Download Completed")
}
}
}
override fun onException(
getRequest: GetRequest?,
exception: NetworkException?,
response: Response<GetRequest, File, Closeable>?
) {
if (exception != null) {
val pauseTaskValue = "10042002"
val cancelTaskValue = "10042003"
val errorMessage = exception.message
errorMessage?.let {
if (!it.contains(pauseTaskValue) && !it.contains(cancelTaskValue)) {
Log.e(TAG, "Error Message:$it")
exception.cause?.let { throwable ->
runOnUiThread {
Toast.makeText(
[email protected],
throwable.message,
Toast.LENGTH_SHORT
)
.show()
}
}
}
}
}
}
}
}
private fun initViewChangeListeners() {
binding.speedLimitSeekBar.setOnSeekBarChangeListener(object :
SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
downloadSpeedLimit = calculateSpeedLimitAsByte(progress)
showDownloadSpeedLimit(progress)
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
}
})
binding.enableSlicedCheckBox.setOnCheckedChangeListener { _, isChecked ->
isEnableSlicedDownload = isChecked
}
}
private fun initButtonClickListeners() {
binding.startDownloadButton.setOnClickListener {
activityResultLauncher.launch(
arrayOf(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
)
)
}
binding.pauseDownloadButton.setOnClickListener {
if (isDownloadManagerInitialized().not()) [email protected]
val requestTaskStatus = downloadManager.getRequestStatus(getRequest.id)
when (requestTaskStatus) {
Result.STATUS.PROCESS -> {
downloadManager.pauseRequest(getRequest.id)
}
else -> {
Toast.makeText(this, "No valid download request", Toast.LENGTH_SHORT).show()
}
}
}
binding.resumeDownloadButton.setOnClickListener {
if (isDownloadManagerInitialized().not()) [email protected]
val requestTaskStatus = downloadManager.getRequestStatus(getRequest.id)
when (requestTaskStatus) {
Result.STATUS.PAUSE -> {
downloadManager.resumeRequest(getRequest, fileRequestCallback)
}
else -> {
Toast.makeText(this, "No download process", Toast.LENGTH_SHORT).show()
}
}
}
binding.cancelDownloadButton.setOnClickListener {
if (isDownloadManagerInitialized().not()) [email protected]
val requestTaskStatus = downloadManager.getRequestStatus(getRequest.id)
when (requestTaskStatus) {
Result.STATUS.PROCESS -> {
downloadManager.cancelRequest(getRequest.id)
clearAllViews()
}
Result.STATUS.PAUSE -> {
downloadManager.cancelRequest(getRequest.id)
clearAllViews()
}
else -> {
Toast.makeText(this, "No valid download request", Toast.LENGTH_SHORT).show()
}
}
}
binding.pasteClipboardImageButton.setOnClickListener {
pasteClipboardData()
}
}
private val activityResultLauncher =
registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
)
{ permissions ->
val allGranted = permissions.entries.map {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
checkSelfPermission(it.key)
} else {
true
}
}.map { it == PackageManager.PERMISSION_GRANTED }.find { !it } ?: true
if (!allGranted) {
Toast.makeText(this, "Permission are not granted", Toast.LENGTH_SHORT).show()
} else {
startDownload()
}
}
private fun startDownload() {
if (this::downloadManager.isInitialized) {
val requestTaskStatus = downloadManager.getRequestStatus(getRequest.id)
when (requestTaskStatus) {
Result.STATUS.PAUSE -> {
Toast.makeText(
this,
"Press Resume Button to continue download process",
Toast.LENGTH_SHORT
).show()
return
}
Result.STATUS.PROCESS -> {
Toast.makeText(
this,
"First cancel the current download process",
Toast.LENGTH_SHORT
).show()
return
}
}
}
downloadManager = DownloadManager.Builder("downloadManager")
.build(this)
val fileName = downloadURL.substringAfterLast("/")
val downloadFilePath = this.cacheDir.path + File.separator + fileName
val currentDownloadURL = binding.urlTextInputEditText.text.toString()
getRequest = DownloadManager.newGetRequestBuilder()
.filePath(downloadFilePath)
.url(currentDownloadURL)
.speedLimit(downloadSpeedLimit)
.enableSlice(isEnableSlicedDownload)
.build()
val result = downloadManager.start(getRequest, fileRequestCallback)
if (result.code != Result.SUCCESS) {
Log.d(TAG, "An Error occurred when downloading")
}
}
private fun convertByteToMb(sizeInByte: Long): String? {
return if (sizeInByte < 0 || sizeInByte == 0L) {
null
} else {
val sizeInMb: Float = sizeInByte / (1024 * 1024).toFloat()
String.format("%.2f", sizeInMb)
}
}
private fun showCurrentDownloadSpeed(speedInByte: Long) {
val downloadSpeedText = if (speedInByte <= 0) {
"-"
} else {
val sizeInKb: Float = speedInByte / 1024.toFloat()
String.format("%.2f", sizeInKb) + "kB/s"
}
binding.currentSpeedTextView.text = downloadSpeedText
}
private fun calculateSpeedLimitAsByte(progressBarValue: Int): Int {
return when (progressBarValue) {
0 -> 512 * 1024
1 -> 1024 * 1024
2 -> 2 * 1024 * 1024
3 -> 4 * 1024 * 1024
4 -> 6 * 1024 * 1024
5 -> 8 * 1024 * 1024
6 -> 16 * 1024 * 1024
7 -> 0
else -> 0
}
}
private fun showDownloadSpeedLimit(progressValue: Int) {
val message = when (progressValue) {
0 -> "512 kB/s"
1 -> "1 mB/s"
2 -> "2 mB/s"
3 -> "4 mB/s"
4 -> "6 mB/s"
5 -> "8 mB/s"
6 -> "16 mB/s"
7 -> "Limitless"
else -> "Error"
}
binding.speedLimitTextView.text = message
}
private fun isDownloadManagerInitialized(): Boolean {
return if (this::downloadManager.isInitialized) {
true
} else {
Toast.makeText(this, "First start the download", Toast.LENGTH_SHORT).show()
false
}
}
private fun pasteClipboardData() {
val clipboardManager = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clipData = clipboardManager.primaryClip
val clipItem = clipData?.getItemAt(0)
val text = clipItem?.text.toString()
if (text == "null") {
Toast.makeText(this, "There is no text on clipboard", Toast.LENGTH_SHORT).show()
} else {
binding.urlTextInputEditText.setText(text)
}
}
private fun showRemainingTime(progress: Progress) {
val elapsedTime = System.nanoTime() - startTime
val allTimeForDownloading =
(elapsedTime * progress.totalSize / progress.finishedSize)
val remainingTime = allTimeForDownloading - elapsedTime
val hours = TimeUnit.NANOSECONDS.toHours(remainingTime)
val minutes = TimeUnit.NANOSECONDS.toMinutes(remainingTime) % 60
val seconds = TimeUnit.NANOSECONDS.toSeconds(remainingTime) % 60
val remainingTimeAsText = if (hours > 0) {
"${hours}h ${minutes}m ${seconds}s left"
} else {
if (minutes > 0) {
"${minutes}m ${seconds}s left"
} else {
"${seconds}s left"
}
}
binding.remainingTimeTextView.text = remainingTimeAsText
}
private fun showSnackBar(rootView: View, message: String) {
val snackBar = Snackbar.make(rootView, message, Snackbar.LENGTH_SHORT)
snackBar.show()
}
private fun clearAllViews() {
binding.percentProgressTextView.text = "0%"
binding.finishedSizeTextView.text = "0"
binding.totalSizeTextView.text = "0"
binding.currentSpeedTextView.text = "0 kB/s"
binding.downloadProgressProgressBar.progress = 0
binding.remainingTimeTextView.text = "0s left"
}
}
Tips & Tricks
According to the Wi-Fi status awareness capability of the Huawei Awareness Kit, you can pause or resume your download task. It will reduce the cost to the user and help to manage your download process properly.
Before starting the download task, you can check that you’re connected to the internet using the ConnectivityManager.
If the download file has the same name as an existing file, it will overwrite the existing file. Therefore, you should give different names for your files.
Even if you minimize the application, the download will continue in the background.
Conclusion
In this article, we have learned how to use Network Kit in your download tasks. And, we’ve developed the Download Manager app that provides many features. In addition to these features, you can also use Network Kit in your upload tasks. Please do not hesitate to ask your questions as a comment.
Thank you for your time and dedication. I hope it was helpful. See you in other articles.
References
Huawei Network Kit Official Documentation
Huawei Network Kit Official Codelab
Huawei Network Kit Official Github
Thanks for sharing.