Related
I've got a ViewPager and a TextView inside it. When content of TextView is larger then it is visible on screen, there shall be possibility to scroll it vertically. But it does not do this automatically
Here is an xml for TextView
Code:
<?xml version="1.0" encoding="utf-8"?>
<TextView
android:id="@+id/txText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#338877"
android:padding="15dp"
android:scrollHorizontally="false"
android:scrollbarAlwaysDrawVerticalTrack="true"
android:scrollbars="vertical"
android:textAppearance="?android:attr/textAppearanceMedium" />
And here is an xml for ViewPager
Code:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:id="@+id/RelativeLayout1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<android.support.v4.view.ViewPager
android:id="@+id/vpPonkSwiper"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_above="@+id/lbPonkFooter" />
<TextView
android:id="@+id/lbPonkFooter"
android:layout_width="fill_parent"
android:layout_height="30dp"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:text="Footer" />
</RelativeLayout>
Here is an Adapter:
Code:
public class PonkPageAdapter extends PagerAdapter
{
private int pagesCount = -1;
private List<Ponk> data;
private App app;
private MockPersistence db;
private Activity activity;
private TextView currentView;
private LayoutInflater inflater;
public PonkPageAdapter(Activity activity, byte section) {
this.activity = activity;
this.app = ((App) activity.getApplication());
this.db = app.getPersistence();
this.section = section;
this.data = db.findPonksBySection(section); // new LinkedList<Ponk>();
this.inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//
this.pagesCount = db.countPonksInSection(section);
}
@Override
public int getCount()
{
return pagesCount;
}
@Override
public Object instantiateItem(View collection, int position)
{
TextView tv = getNewView();
Ponk j = data.get(position);
tv.setText(j.text);
tv.setTag(j.id);
setFontSizeInSP(tv, app.getFontSize());
tv.setTag(position);
((ViewPager) collection).addView(tv, 0);
return tv;
}
private TextView getNewView()
{
LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
TextView tv = (TextView)inflater.inflate(R.layout.Ponk, null);
return tv;
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object)
{
currentView = (TextView) object;
}
@Override
public void destroyItem(View collection, int position, Object view)
{
((ViewPager) collection).removeView((TextView) view);
}
@Override
public boolean isViewFromObject(View view, Object object)
{
return view == (TextView) object;
}
@Override
public Parcelable saveState()
{
return null;
}
}
This article is originally from HUAWEI Developer Forum
Forum link: https://forums.developer.huawei.com/forumPortal/en/home
HiAi Image Super Resolution
Upscales an image or reduces image noise and improves image details without changing the resolution.
{
"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"
}
Upscales an image or reduces image noise and improves image details without changing the resolution.
Base on AI deep learning of CV (Computer Vision)
Utilize Huawei NPU (Neural Processing Unit), 50X faster than CPU
1X & 3X super-resolution furnish images with clearer effect, reducing JPEG compression noise
You can check the offical documentation about HiAi Image super resolution.
Huawei continuous investment on NPU technology
Huawei Phones support HiAI
Software: Huawei EMUI 9.0 & above
Hardware: CPU 970,810, 820,985,990
Codelab
https://developer.huawei.com/consumer/en/codelab/HiAIImageSuperresolution/index.html#0
You can also follow the codelab to implement the HiAi image resolution with the help DevEco IDE plugin in Android Studio.
Project: (HiAi Image Super Resolution)
In this article we are going to make project in which we can implement HiAi Image Super Resolution to improve low resolution image quality which is used in most of the application as a thumbnail images.
1. Implementation:
Download the vision-oversea-release.aar package in the Huawei AI Engine SDKs from the Huawei developer community.
Copy the downloaded vision-oversea-release.aar package to the app/libs directory of the project.
Add the following code to build.gradle in the APP directory of the project, and add vision-release.aar to the project. Dependency on the Gson library must be added, because the conversion of parameters and results between the JavaScript Object Notation (JSON) and Java classes inside vision-release.aar depends on the Gson library.
Code:
repositories {
flatDir {
dirs 'libs'
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation(name: 'vision-oversea-release', ext: 'aar')
implementation 'com.google.code.gson:gson:2.8.6'
}
2. Assets:
In this section we adding some low resolution images in "assets/material/image_super_resolution" directory, to further fetch images for local direction for optimization.
3. Design ListView:
In this section we are design ListView in our layouts to show original images and optimized images.
activity_main.xml
Code:
<?xml version="1.0" encoding="utf-8"?>
<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"
tools:context=".MainActivity">
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="1pt"
android:layout_marginBottom="5pt"
android:orientation="horizontal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="Original Image"
android:textSize="24sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="Improved Image"
android:textSize="24sp" />
</LinearLayout>
<ListView
android:id="@+id/item_listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:dividerHeight="3pt"
/>
</LinearLayout>
items.xml
Code:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ImageView
android:id="@+id/imgOriginal"
android:layout_width="100dp"
android:layout_height="100dp"
app:srcCompat="@drawable/noimage"
android:layout_gravity="start"
android:layout_weight="1"
/>
<TextView
android:id="@+id/imgTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" "
android:layout_gravity="center_horizontal"
android:layout_weight="0"
android:textAlignment="center"
/>
<ImageView
android:id="@+id/imgConverted"
android:layout_width="100dp"
android:layout_height="100dp"
app:srcCompat="@drawable/noimage"
android:layout_gravity="end"
android:layout_weight="1"
app:layout_constraintDimensionRatio="h,4:3"
/>
</LinearLayout>
4. Coding: (Adapter, HiAi Image Super Resolution )
Util Class:
We make a util class AssetsFileUtil, to get all the images from local assets directory, get single BitMap image.
Code:
public class AssetsFileUtil {
public static Bitmap getBitmapByFilePath(Context context, String filePath){
try{
AssetManager assetManager = context.getAssets();
InputStream is = assetManager.open(filePath);
Bitmap bitmap = BitmapFactory.decodeStream(is);
return bitmap;
}catch (Exception e){
e.printStackTrace();
return null;
}
}
public static List<Bitmap> getBitmapListByDirPath(Context context, String dirPath){
List<Bitmap> list = new ArrayList<Bitmap>();
try{
AssetManager assetManager = context.getResources().getAssets();
String[] photos = assetManager.list(dirPath);
for(String photo : photos){
if(isFile(photo)){
Bitmap bitmap = getBitmapByFilePath(context,dirPath + "/" + photo);
list.add(bitmap);
}else {
List<Bitmap> childList = getBitmapListByDirPath(context,dirPath + "/" + photo);
list.addAll(childList);
}
}
}catch (Exception e){
e.printStackTrace();
}
return list;
}
public static List<String> getFileNameListByDirPath(Context context, String dirPath){
List<String> list = new ArrayList<String>();
try{
AssetManager assetManager = context.getResources().getAssets();
String[] photos = assetManager.list(dirPath);
for(String photo : photos){
if(isFile(photo)){
list.add(dirPath + "/" + photo);
}else {
List<String> childList = getFileNameListByDirPath(context,dirPath + "/" + photo);
list.addAll(childList);
}
}
}catch (Exception e){
e.printStackTrace();
}
return list;
}
public static boolean isFile(String fileName){
if(fileName.contains(".")){
return true;
}else {
return false;
}
}
}
MainActivity Class:
In this class we are getting local images and attaching images list to our Adapter
Code:
public class MainActivity extends AppCompatActivity {
private String mDirPath;
private ArrayList<Item> itemList;
private List<String> imageList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
itemList = new ArrayList<Item>();
getLocalImages();
// Setting Adapter and listview
ItemAdapter itemAdapter = new ItemAdapter(getApplicationContext(), R.layout.items, itemList);
ListView listView = findViewById(R.id.item_listView);
listView.setAdapter(itemAdapter);
}
public void getLocalImages(){
mDirPath ="material/image_super_resolution";
imageList = AssetsFileUtil.getFileNameListByDirPath(this,mDirPath);
for(int i=0; i<imageList.size();i++){
itemList.add(new Item(imageList.get(i), " ", imageList.get(i)));
}
}
}
Item Class:
Prepare item data class.
Code:
public class Item {
private String imgOriginal;
private String imgTitle;
private String imgConverted;
public Item(String imgOriginal, String imgTitle, String imgConverted) {
this.imgOriginal = imgOriginal;
this.imgTitle = imgTitle;
this.imgConverted = imgConverted;
}
public String getImgOriginal() {
return imgOriginal;
}
public void setImgOriginal(String imgOriginal) {
this.imgOriginal = imgOriginal;
}
public String getImgTitle() {
return imgTitle;
}
public void setImgTitle(String imgTitle) {
this.imgTitle = imgTitle;
}
public String getImgConverted() {
return imgConverted;
}
public void setImgConverted(String imgConverted) {
this.imgConverted = imgConverted;
}
}
ItemAdapter Class:
In this class we are binding our images array list with our layout ImageView and implement HiAi Image Resolution to optimize image resolution.
ItemAdapter class extends ArrayAdapter with the type of Item class.
Code:
public class ItemAdapter extends ArrayAdapter<Item>
Define some constants
Code:
private ArrayList<Item> itemList;
private final static int SUPERRESOLUTION_RESULT = 110;
private Bitmap bitmapOriginal;
private Bitmap bitmapConverted;
ImageView imgOriginal;
ImageView imgConverted;
private String TAG = "ItemAdapter";
Prepare constructor for the Adpater class
Code:
public ItemAdapter(@NonNull Context context, int resource, @NonNull ArrayList<Item> itemList) {
super(context, resource, itemList);
this.itemList = itemList;
}
Define initHiAi function to check service connected or disconnected
Code:
/**
* init HiAI interface
*/
private void initHiAI() {
/** Initialize with the VisionBase static class and asynchronously get the connection of the service */
VisionBase.init(getContext(), new ConnectionCallback() {
@Override
public void onServiceConnect() {
/** This callback method is invoked when the service connection is successful; you can do the initialization of the detector class, mark the service connection status, and so on */
}
@Override
public void onServiceDisconnect() {
/** When the service is disconnected, this callback method is called; you can choose to reconnect the service here, or to handle the exception*/
}
});
}
Define setHiAi function to perform HiAi operation on Original image and generate the optimized Bitmap.
Code:
/**
* Capability Interfaces
*
* @return
*/
private void setHiAi() {
/** Define class detector, the context of this project is the input parameter */
ImageSuperResolution superResolution = new ImageSuperResolution(getContext());
/** Define the frame, put the bitmap that needs to detect the image into the frame*/
Frame frame = new Frame();
/** BitmapFactory.decodeFile input resource file path*/
// Bitmap bitmap = BitmapFactory.decodeFile(null);
frame.setBitmap(bitmapOriginal);
/** Define and set super-resolution parameters*/
SuperResolutionConfiguration paras = new SuperResolutionConfiguration(
SuperResolutionConfiguration.SISR_SCALE_3X,
SuperResolutionConfiguration.SISR_QUALITY_HIGH);
superResolution.setSuperResolutionConfiguration(paras);
/** Run super-resolution and get result of processing */
ImageResult result = superResolution.doSuperResolution(frame, null);
/** After the results are processed to get bitmap*/
Bitmap bmp = result.getBitmap();
/** Note: The result and the Bitmap in the result must be NULL, but also to determine whether the returned error code is 0 (0 means no error)*/
this.bitmapConverted = bmp;
handler.sendEmptyMessage(SUPERRESOLUTION_RESULT);
}
Define Handler if the optimization completed attach the optimized Bitmap to image.
Code:
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case SUPERRESOLUTION_RESULT:
if (bitmapConverted != null) {
imgConverted.setImageBitmap(bitmapConverted);
} else { // Set the original image
imgConverted.setImageBitmap(bitmapOriginal);
// toast("High Resolution image");
}
break;
}
}
};
Override the getView method attached orginial image to image view and process the original image to attached optimized image.
Code:
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
initHiAI();
int itemIndex = position;
if(convertView == null){
convertView = LayoutInflater.from(getContext()).inflate(R.layout.items,parent, false);
}
imgOriginal = convertView.findViewById(R.id.imgOriginal);
TextView imgTitle = convertView.findViewById(R.id.imgTitle);
imgConverted = convertView.findViewById(R.id.imgConverted);
bitmapOriginal = AssetsFileUtil.getBitmapByFilePath(imgOriginal.getContext(), itemList.get(itemIndex).getImgConverted());
imgOriginal.setImageBitmap(bitmapOriginal);
bitmapConverted =AssetsFileUtil.getBitmapByFilePath(imgConverted.getContext(), itemList.get(itemIndex).getImgOriginal());
imgConverted.setImageBitmap(bitmapConverted);
int height = bitmapOriginal.getHeight();
int width = bitmapOriginal.getWidth();
Log.e(TAG, "width:" + width + ";height:" + height);
if (width <= 800 && height <= 600) {
new Thread() {
@Override
public void run() {
setHiAi();
}
}.start();
} else {
toast("Width and height of the image cannot exceed 800*600");
}
imgTitle.setText(itemList.get(itemIndex).getImgTitle());
return convertView;
}
public void toast(String text) {
Toast.makeText(getContext(), text, Toast.LENGTH_SHORT).show();
}
Coding section has been complete here, now run you project and check the output of the Image Optimization by using HiAi Image Super Resolution.
5. Result
In this article I will explain how to develop a basic messaging app by Huawei Push Kit. If you want to develop a messaging app or add messaging section into your application, I hope this article will be useful for you.
Before beginning, this application and article will be addition of my first ones. If you have not seen that article, you can see the link below. It also shows how to integrate Push Kit into your Xamarin.Android Application.
https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0201314061860830181&fid=0101187876626530001
Layout of activity_main
We will only add our messaging button to pass to messaging section. Final version of “activity_main.xml” will be like this.
Code:
<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:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:weightSum="100"
android:id="@+id/linearLayoutMain">
<Button
android:text="Get Token"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="25px"
android:layout_weight="15"
android:id="@+id/btnGetToken" />
<Button
android:text="Send Test Notification"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="25px"
android:layout_weight="15"
android:visibility="invisible"
android:id="@+id/btnNotification" />
<Button
android:text="Send Test Data Message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="25px"
android:layout_weight="15"
android:visibility="invisible"
android:id="@+id/btnDataMessage" />
<Button
android:text="Messaging"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="25px"
android:layout_weight="15"
android:visibility="invisible"
android:id="@+id/btnMessagingMain" />
</LinearLayout>
MainActivity Class
Firstly, we will inherit ViewAnimator.IOnClickListener interface to this class. Then, we will organize inside of OnCreate Method and implement OnClick method.
Code:
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
SetContentView(Resource.Layout.activity_main);
//Identifying Buttons
btnGetToken = FindViewById<Button>(Resource.Id.btnGetToken);
btnNotification = FindViewById<Button>(Resource.Id.btnNotification);
btnDataMessage = FindViewById<Button>(Resource.Id.btnDataMessage);
btnMessagingMain = FindViewById<Button>(Resource.Id.btnMessagingMain);
//Set onClick listener to buttons
btnGetToken.SetOnClickListener(this);
btnNotification.SetOnClickListener(this);
btnDataMessage.SetOnClickListener(this);
btnMessagingMain.SetOnClickListener(this);
//Create instance of Broadcast Receiver for data message service
myReceiver = new MyBroadcastReceiver();
CheckPermission(new string[] { Android.Manifest.Permission.Internet,
Android.Manifest.Permission.AccessNetworkState,
Android.Manifest.Permission.WakeLock}, 100);
}
Secondly, we will arrange OnClick method so that we can call these events from one method.
Code:
public void OnClick(View v)
{
try
{
switch (v.Id)
{
case Resource.Id.btnGetToken:
GetToken();
break;
case Resource.Id.btnNotification:
HmsPushKit.SendNotification(tokenFromGetToken, "Title", "Body");
break;
case Resource.Id.btnDataMessage:
HmsPushKit.SendDataMessage(tokenFromGetToken, "{\"Type\":\"Test\",\"Message\":\"Welcome\"}");
break;
case Resource.Id.btnMessagingMain:
StartMessagingMain();
break;
default:
break;
}
}
catch (Exception e)
{
Log.Error("OnClickListener", e.ToString());
}
}
Thirdly, as you guess we will add StartMessagingMain method to call MessagingMainActivity class. Calling StartActivity method is adequate here. We don’t have to send any Intent.
Finally, we will move AccessToken, SendDataMessage and SendNotification method to new HmsPushKit class.
HmsPushKit.cs
We should be able to call these methods from any other class. Because of that, methods in this class should be static.
Code:
public static class HmsPushKit
{
static readonly string appID = "AppID"; //AppGallery Connect > Project Setting > App information > App ID
static readonly string appKey = "AppKey"; //AppGallery Connect > Project Setting > App information > App key
static readonly HttpClient client = new HttpClient();
public static async Task<string> GetAccessToken()
{
string uri = "https://oauth-login.cloud.huawei.com/oauth2/v3/token";
var values = new Dictionary<string, string>
{
{ "grant_type", "client_credentials" },
{ "client_id", appID },
{ "client_secret", appKey }
};
var content = new FormUrlEncodedContent(values);
var response = await client.PostAsync(uri, content);
var jsonResponse = JObject.Parse(await response.Content.ReadAsStringAsync()); //Install-Package Newtonsoft.Json
string accessToken = jsonResponse["access_token"].ToString(); //It is valid for 3600 seconds
return accessToken;
}
/// <summary>
/// Send notification by Huawei Push Kit.
/// </summary>
/// <param name="tokenUSendTo">This token get by GetToken function</param>
/// <param name="notTitle">Notification Title</param>
/// <param name="notBody">Notification Body</param>
public static async void SendNotification(string tokenUSendTo,string notTitle,string notBody)
{
string uriNot = "https://push-api.cloud.huawei.com/v1/" + appID + "/messages:send";
var jObject = new
{
message = new
{
notification = new
{
title = notTitle,
body = notBody
},
android = new
{
notification = new
{
click_action = new
{
type = 3
}
}
},
token = new[] { tokenUSendTo }
}
};
string myJson = JsonConvert.SerializeObject(jObject, Formatting.Indented);
client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", await GetAccessToken());
var responseData = await client.PostAsync(uriNot, new StringContent(myJson, Encoding.UTF8, "application/json"));
}
/// <summary>
/// Send data message by Huawei Push Kit.
/// </summary>
/// <param name="tokenUSendTo">This token get by GetToken function</param>
/// <param name="serializedObject">Use JsonConvert.SerializeObject function</param>
public static async void SendDataMessage(string tokenUSendTo, string serializedObject)
{
string uriNot = "https://push-api.cloud.huawei.com/v1/" + appID + "/messages:send";
var jObject = new
{
message = new
{
data = serializedObject,
token = new[] { tokenUSendTo }
}
};
string myJson = JsonConvert.SerializeObject(jObject, Formatting.Indented);
client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", await GetAccessToken());
var responseData = await client.PostAsync(uriNot, new StringContent(myJson, Encoding.UTF8, "application/json"));
}
}
DataBase
Actually, database is not essential for a sample application but I want to make it pretty. I will use sqlite-net-pcl by SQLite-net.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
This application will need two essential database tables(Model) Person and Message. We will also use UniqueData table for minor data.
Code:
public class Person
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string Icon { get; set; }
public string Token { get; set; }
public Person(string Code,string Name)
{
this.Code = Code;
this.Name = Name;
Random rnd = new Random();
Icon = "icon" + rnd.Next(1, 4);
}
public Person()
{
}
}
public class Message
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string CodeFrom { get; set; }
public string CodeTo { get; set; }
public string Text { get; set; }
}
public class UniqueData
{
public string Key { get; set; }
public string Value { get; set; }
}
And the Database class that I use is below. The most part is easy to understand. If you want to use another Model you should add this into CreateDataBase method. Moreover, you don’t have to check if there is a database. If database does not exist, it will create otherwise it will not.
Code:
public class DataBase
{
readonly string dbPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), "DB.db");
readonly string _SQLite = "SQLite";
public DataBase()
{
CreateDataBase();
}
public bool CreateDataBase()
{
try
{
using (var connection = new SQLiteConnection(dbPath))
{
connection.CreateTable<Person>();
connection.CreateTable<Message>();
connection.CreateTable<UniqueDatas>();
return true;
}
}
catch (SQLiteException e)
{
Log.Error(_SQLite, e.Message);
return false;
}
}
public bool InsertIntoTable<T>(T row)
{
try
{
using (var connection = new SQLiteConnection(dbPath))
{
connection.Insert(row);
return true;
}
}
catch (SQLiteException e)
{
Log.Error(_SQLite, e.Message);
return false;
}
}
public List<T> SelectTable<T>() where T:new()
{
try
{
using (var connection = new SQLiteConnection(dbPath))
{
return connection.Table<T>().ToList();
}
}
catch (SQLiteException e)
{
Log.Error(_SQLite, e.Message);
return null;
}
}
public bool DeleteFromTable<T>(T row)
{
try
{
using (var connection = new SQLiteConnection(dbPath))
{
connection.Delete(row);
return true;
}
}
catch (SQLiteException e)
{
Log.Error(_SQLite, e.Message);
return false;
}
}
}
Layout of activity_main_messaging
I will share with you a simple screen for the list of people.
My xml file will not contain the rows. We will add them from MessagingMainActivity class. Xml code for this layout below.
Code:
<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:orientation="vertical"
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@color/material_grey_300"
android:weightSum="4"
android:paddingHorizontal="2dp">
<Button
android:text="Del"
android:gravity="center_horizontal"
android:textSize="24dp"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/btnDel"/>
<TextView
android:text="Chats"
android:gravity="center"
android:textStyle="bold"
android:textColor="@android:color/black"
android:textSize="24dp"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="4"
android:id="@+id/textView1"
/>
<Button
android:gravity="center_horizontal"
android:text="New"
android:textSize="24dp"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/btnNew"/>
</LinearLayout>
<ScrollView
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:scrollbars="vertical"
android:paddingHorizontal="20dp"
android:id="@+id/scrollView">
<LinearLayout
android:orientation="vertical"
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/linearLayout"
android:divider="#B6B6B6"
android:dividerHeight="5px">
</LinearLayout>
</ScrollView>
</LinearLayout>
MessagingMainActivity Class
Things we will do in this class are bringing people from database, adding new person, deleting person and starting messaging. So let’s start with identifing Delete Button, New button and LinearLayout which is in the ScrollView.
After, we will set ClickListener on two buttons therefore, we will inherit ViewAnimator.IOnClickListener to this activity as well.
Later, we need to take instance of our DataBase class then add people into our LinearLayout. Furthermore, I will also add sample data.
At last, we will fill OnClick method.
Code:
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.activity_main_messaging);
linearLayout = FindViewById<LinearLayout>(Resource.Id.linearLayout);
btnNew = FindViewById<Button>(Resource.Id.btnNew);
btnDel = FindViewById<Button>(Resource.Id.btnDel);
btnNew.SetOnClickListener(this);
btnDel.SetOnClickListener(this);
db = new DataBase();
if (db.SelectTable<Person>().Count == 0)
{
db.InsertIntoTable(new Person { Code = "0", Name = "Enes Durmus", Icon = "icon1" });
db.InsertIntoTable(new Person { Code = "1", Name = "Ahmet Ercek", Icon = "icon2" });
db.InsertIntoTable(new Person { Code = "2", Name = "Gokberk Bardakci", Icon = "icon3" });
}
foreach (Person person in db.SelectTable<Person>())
{
linearLayout.AddView(OneRowLL(this, person));
Log.Info("SQLite", person.ToString());
linearLayout.AddView(GetLine(this, Resources.DisplayMetrics.WidthPixels / 3));// This is just decorative
}
}
public void OnClick(View v)
{
try
{
switch (v.Id)
{
case Resource.Id.btnNew:
AddPerson();
break;
case Resource.Id.btnDel:
IsDelete = true;
break;
default:
break;
}
}
catch (Exception e)
{
Log.Error("OnClickListener", e.ToString());
}
}
As you see there is OneRowLL method. It returns a linearlayout which contains CircleImageView from Refractored.Controls.CircleImage nuget and linearlayout contains two textviews.
Code:
LinearLayout OneRowLL(Context context, Person person)
{
LinearLayout result = new LinearLayout(context);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MatchParent, LinearLayout.LayoutParams.WrapContent);
result.LayoutParameters = layoutParams;
result.Orientation = Orientation.Horizontal;
result.TransitionName = person.Code;
result.WeightSum = 1;
ViewGroup.LayoutParams vgLayoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.MatchParent);
CircleImageView imageView = new CircleImageView(context); // Nuget: Refractored.Controls.CircleImageView
LinearLayout.LayoutParams iLayoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WrapContent, LinearLayout.LayoutParams.MatchParent) { Weight = 0.6f };
imageView.LayoutParameters = iLayoutParams;
int id = Resources.GetIdentifier("com.companyname.pushdemoforarticle:drawable/" + person.Icon, null, null);
imageView.SetImageResource(id);
imageView.BorderWidth = 6;
imageView.BorderColor = -16777216;
result.AddView(imageView);
LinearLayout subLinearLayout = new LinearLayout(context);
layoutParams.Weight = 1;
subLinearLayout.LayoutParameters = layoutParams;
subLinearLayout.Orientation = Orientation.Vertical;
subLinearLayout.SetPadding(20, 0, 0, 0);
ViewGroup.LayoutParams txtViewLayoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent);
subLinearLayout.AddView(new TextView(context)
{
Text = person.Name,
TextSize = 24,
LayoutParameters = txtViewLayoutParams,
Gravity = GravityFlags.Start
});
string _lastMessage = string.Empty;
List<Message> messages = db.SelectTable<Message>().Where(x => x.CodeFrom == person.Code || x.CodeTo == person.Code).ToList();
if (messages.Count != 0)
{
Message lastMessage = messages.TakeLast(1).First();
_lastMessage = lastMessage.CodeFrom == self ? "You: "+lastMessage.Text : lastMessage.Text;
}
subLinearLayout.AddView(new TextView(context)
{
Text = _lastMessage,
LayoutParameters = txtViewLayoutParams,
});
result.AddView(subLinearLayout);
result.Click += LinearLayoutClick;
return result;
}
Afterwards, we will create AddPerson method for btnNew. We will use LayoutInflater so I also create a xml file as well. We need user’s code to get his token and a name to display it.
Code:
private void AddPerson()
{
AlertDialog dialog = InitDialog();
dialog.Show();
}
public AlertDialog InitDialog()
{
//Setting xml from layouts
LayoutInflater layoutInflater = LayoutInflater.From(this);
View view = layoutInflater.Inflate(Resource.Layout.input_box, null);
AlertDialog.Builder alertbuilder = new AlertDialog.Builder(this);
alertbuilder.SetView(view);
//Identifing EditTexts
var userCode = view.FindViewById<EditText>(Resource.Id.txtUserCode);
var userName = view.FindViewById<EditText>(Resource.Id.txtUserName);
//Setting Positive and Negative Buttons
alertbuilder.SetCancelable(false)
.SetPositiveButton("OK", delegate
{
Person person = new Person(userCode.Text.Trim(), userName.Text);
if (db.InsertIntoTable(person))
linearLayout.AddView(OneRowLL(this, person));
}).SetNegativeButton("Cancel", delegate { });
AlertDialog dialog = alertbuilder.Create();
return dialog;
}
Code:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/dialogTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add Your Friend"
android:textAppearance="?android:attr/textAppearanceLarge" />
<EditText
android:id="@+id/txtUserCode"
android:inputType="text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter Your Friend's Code" />
<EditText
android:id="@+id/txtUserName"
android:inputType="text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter Your Friend's Name" />
</LinearLayout>
Next step is btnDel. Delete button will be simple. Click delete then click person afterwards person is gone. That’s adequate for now but you can improve it by adding invisible red delete button to each row and ask for approve.
Finally, LinearLayoutClick for pass to messaging. We will send their code by Intent.
Code:
public void LinearLayoutClick(object sender, EventArgs e)
{
LinearLayout row = sender as LinearLayout;
if (!IsDelete)
{
Intent intent = new Intent(this, typeof(MessagingActivity));
intent.PutExtra("code", row.TransitionName);
StartActivity(intent);
}
else
{
linearLayout.RemoveView(row);
IsDelete = false;
}
}
Layout of activity_messaging.xml
There will be 3 sections. First one is the LinearLayout which shows who we message. Second one is ScrollView for messages. And the last one is EditText for typing and sending. Xml file below.
Code:
<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:orientation="vertical"
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:weightSum="100"
android:id="@+id/linearLayoutMain">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="100"
android:id="@+id/ll0"
android:transitionName="0"
android:paddingLeft="@dimen/abc_select_dialog_padding_start_material">
<refractored.controls.CircleImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="60"
app:civ_border_width="2dp"
app:civ_border_color="#000000"
android:id="@+id/imageIcon"/>
<LinearLayout
android:paddingLeft="10dp"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="100">
<TextView
android:textSize="24dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:id="@+id/txtName"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtStatus"/>
</LinearLayout>
</LinearLayout>
<ScrollView
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="100"
android:gravity="bottom"
android:animateLayoutChanges="true"
android:orientation="vertical"
android:scrollbars="vertical"
android:paddingHorizontal="20dp"
android:id="@+id/scrollView">
<LinearLayout
android:orientation="vertical"
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/linearLayout" />
</ScrollView>
<android.support.design.widget.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="20"
android:id="@+id/textInput"
android:hint="Type here!"
android:layout_marginBottom="4dp"/>
</LinearLayout>
For more information, you can visit https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0202327073597950015&fid=0101187876626530001
Overview
This application helps us for getting the direction from current location to the selected place. This app uses Huawei Site Kit, Location Kit, Map kit and Huawei Direction API for showing the direction. Let us see the uses of all kits in this application.
Site Kit: This kit is used for getting the places and near-by places on keyword search.
Location Kit: This kit is used for getting the current location of the user.
Map Kit: This kit is used for showing the map, adding a marker and drawing polyline on the Huawei Map.
Direction API: This API is used for getting the path, steps and polyline between two places.
Let us start with the project configuration part:
Step 1: Create an app on App Gallery Connect.
Step 2: Enable the Site Kit, Location Lit and Map Kit in Manage APIs menu.
{
"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"
}
Step 3: Create an Android Project with the same package name as App Gallery project package name.
Step 4: Enter the below maven url inside the repositories of buildscript and allprojects (project build.gradle file).
Java:
maven { url ‘http://developer.huawei.com/repo/’ }
Step 5: Add classpath to project’s build.gradle file.
Java:
dependencies {
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath 'com.huawei.agconnect:agcp:1.3.1.300'
}
Step 6: Apply plugin in App’s build.gradle file at top after application plugin.
Java:
apply plugin: 'com.huawei.agconnect'
Step 7: Add below dependencies to app’s build.gradle file.
Java:
implementation 'com.huawei.hms:site:5.0.2.300'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation "androidx.cardview:cardview:1.0.0"
implementation 'com.huawei.hms:maps:4.0.0.302'
implementation 'com.huawei.hms:location:4.0.1.300'
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
implementation 'com.google.code.gson:gson:2.6.1'
Step 8: Add the app ID generated when the creating the app on HUAWEI Developers to manifest file.
Java:
<meta-data
android:name="com.huawei.hms.client.appid"
android:value="appid=your app id" />
Step 9: Add the below permissions to manifest file.
Java:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="com.huawei.appmarket.service.commondata.permission.GET_COMMON_DATA"/>
<!-- Allow the app to obtain the coarse longitude and latitude of a user through the Wi-Fi network or base station. -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!-- Allow the app to receive location information from satellites through the GPS chip. -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
Step 10: Generate SHA 256 key and add to App Gallery Connect Project.
Step 11: download the agconnect-services.json from App Information Section. Copy and paste the Json file in the app folder of the android project.
Step 12: Sync the project.
Let us start with the implementation part:
Part 1: Site Kit Integration
Using the Site Kit, we will search for place and get the latitude and longitude.
Step 1: Get the API_KEY from App Gallery and define the same in your MainActivity.Java.
Java:
public static final String MY_API_KEY = "Your API_KEY will come here";
Step 2: Declare a SearchService object and use SearchServiceFactory to initialize the object.
Java:
// Declare SearchService object
private SearchService searchService;
// Initialize the SearchService object
searchService = SearchServiceFactory.create(this, URLEncoder.encode(MY_API_KEY, "utf-8"));
Step 3: create the layout for search a place.
Java:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_gravity="bottom"
android:gravity="center"
android:paddingLeft="5dp"
android:text="Find your place"
android:textSize="18sp"
android:textStyle="bold"
android:visibility="visible" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Query: "
android:visibility="gone" />
<EditText
android:id="@+id/edit_text_text_search_query"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_weight="1"
android:autofillHints=""
android:background="@drawable/search_bg"
android:hint="Search here "
android:imeOptions="actionGo"
android:inputType="text"
android:paddingLeft="10dp"
android:paddingTop="5dp"
android:paddingRight="10dp"
android:paddingBottom="5dp"
android:visibility="visible"/>
</LinearLayout>
<Button
android:id="@+id/button_text_search"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:background="@drawable/search_btn_bg"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:text="Search"
android:textAllCaps="false"
android:textColor="@color/upsdk_white" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:gravity="center"
android:paddingLeft="5dp"
android:text="Note: Get site Id suing Keyword/Nearby/Place suggestion search"
android:textSize="18sp"
android:textStyle="bold"
android:visibility="gone"
android:padding="10dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_gravity="bottom"
android:background="#D3D3D3"
android:gravity="center_vertical"
android:paddingLeft="5dp"
android:text="Result"
android:textSize="16sp"
android:visibility="gone" />
<TextView
android:id="@+id/response_text_search"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textIsSelectable="true"
android:padding="10dp"
android:textColor="@color/colorPrimary"
android:textSize="18sp"
android:visibility="gone" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/searchResultList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:visibility="visible"/>
</LinearLayout>
</LinearLayout>
Step 4: Create the ListAdapter for showing the data.
Java:
public class SearchListAdapter extends RecyclerView.Adapter<SearchListAdapter.SearchViewHolder> {
List<SearchModel> searchModelList;
Context context;
public SearchListAdapter(List<SearchModel> searchModelList, Context context) {
this.searchModelList = searchModelList;
this.context = context;
}
@NonNull
@Override
public SearchViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new SearchViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.search_result_item, parent, false));
}
@Override
public void onBindViewHolder(@NonNull SearchViewHolder holder, final int position) {
final SearchModel searchModel = searchModelList.get(position);
holder.nameTv.setText(searchModel.getName());
holder.formattedAddress.setText(searchModel.getFormattedAddress());
holder.countryCodeTv.setText(searchModel.getCountryCode());
holder.countryTv.setText(searchModel.getCountry());
// Click listener for Row view
holder.btnGetDirection.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context,"Position is "+position,Toast.LENGTH_SHORT ).show();
Intent intent = new Intent(context, DirectionActivity.class);
intent.putExtra("latitude",searchModel.getLatitude());
intent.putExtra("longitude",searchModel.getLongitude());
context.startActivity(intent);
}
});
}
@Override
public int getItemCount() {
return searchModelList.size();
}
class SearchViewHolder extends RecyclerView.ViewHolder {
TextView nameTv;
TextView formattedAddress;
TextView countryTv;
TextView countryCodeTv;
LinearLayout row_layout;
Button btnGetDirection;
public SearchViewHolder(@NonNull View itemView) {
super(itemView);
nameTv = itemView.findViewById(R.id.name);
formattedAddress = itemView.findViewById(R.id.formattedAddress);
countryTv = itemView.findViewById(R.id.country);
countryCodeTv = itemView.findViewById(R.id.countryCode);
row_layout = itemView.findViewById(R.id.row_layout);
btnGetDirection = itemView.findViewById(R.id.get_direction);
}
}
}
Step 5: Create row_layout.xml inside layout folder.
Java:
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="5dp"
app:cardElevation="5dp"
android:layout_marginBottom="3dp">
<LinearLayout
android:id="@+id/row_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Name: "
android:textStyle="bold"
android:layout_weight="0.3"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.7"
android:paddingLeft="5dp"
android:id="@+id/name"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Address: "
android:textStyle="bold"
android:layout_weight="0.3"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:id="@+id/formattedAddress"
android:layout_weight="0.7"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Country: "
android:textStyle="bold"
android:layout_weight="0.3"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:id="@+id/country"
android:layout_weight="0.7"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Country code: "
android:textStyle="bold"
android:layout_weight="0.3"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/countryCode"
android:paddingLeft="5dp"
android:layout_weight="0.3"/>
<Button
android:id="@+id/get_direction"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_gravity="center"
android:background="@drawable/search_btn_bg"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:text="Get Direction"
android:textAllCaps="false"
android:textColor="@color/upsdk_white" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
Step 6: Initialize the Recycler view to MainActivity.Java.
Java:
private RecyclerView searchResultList;
searchResultList.setLayoutManager(new LinearLayoutManager(this));
Step 7: On Search button click, search places and set it to ListAdapter.
Java:
mSearchBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
searchModelList = new ArrayList<>();
search();
}
});
Java:
public void search() {
TextSearchRequest textSearchRequest = new TextSearchRequest();
textSearchRequest.setQuery(queryInput.getText().toString());
textSearchRequest.setHwPoiType(HwLocationType.ADDRESS);
textSearchRequest.setHwPoiType(HwLocationType.ENTERTAINMENT_PLACE);
textSearchRequest.setHwPoiType(HwLocationType.INDIAN_RESTAURANT);
textSearchRequest.setHwPoiType(HwLocationType.CITIES);
textSearchRequest.setHwPoiType(HwLocationType.REGIONS);
searchService.textSearch(textSearchRequest, new SearchResultListener<TextSearchResponse>() {
@Override
public void onSearchResult(TextSearchResponse textSearchResponse) {
List<Site> sites = textSearchResponse.getSites();
if (sites == null || sites == null || sites.size() <= 0) {
return;
}
AddressDetail addressDetail;
if (sites != null && sites.size() > 0) {
for (Site site : sites) {
searchModel = new SearchModel();
addressDetail = site.getAddress();
searchModel.setName(site.getName());
searchModel.setFormattedAddress(site.getFormatAddress());
searchModel.setCountry(addressDetail.getCountry());
searchModel.setCountryCode(addressDetail.getCountryCode());
searchModel.setLatitude(site.getLocation().getLat());
searchModel.setLongitude(site.getLocation().getLng());
searchModelList.add(searchModel);
}
SearchListAdapter searchListAdapter = new SearchListAdapter(searchModelList, MainActivity.this);
searchResultList.setAdapter(searchListAdapter);
}
}
Now getting the list of places completed.
Result:
Part 2: Map Kit Implementation
This Kit is being used for showing the Huawei map. After clicking on Get Direction button in searched places, its navigates to DirectionActivity.Java which loads the Huawei map using Map Kit and getting the current location using Huawei Location Kit.
Step 1: Create the xml layout which contains the MapView.
XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.huawei.hms.maps.MapView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:map="http://schemas.android.com/apk/res-auto"
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent"
map:mapType="normal"
map:uiCompass="true"/>
</LinearLayout>
Step 2: To use the MapView, implement OnMapReadyCallbackAPI and override the onMapReady(HuaweiMap huaweiMap).
Java:
public class DirectionActivity extends AppCompatActivity implements OnMapReadyCallback{
}
Step 3: Add runtime permissions.
Java:
private static final String[] RUNTIME_PERMISSIONS = {
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.INTERNET
};
if (!hasPermissions(this, RUNTIME_PERMISSIONS)) {
ActivityCompat.requestPermissions(this, RUNTIME_PERMISSIONS, REQUEST_CODE);
}
Java:
private static boolean hasPermissions(Context context, String... permissions) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && permissions != null) {
for (String permission : permissions) {
if (ActivityCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
}
return true;
}
Step 4: Load MapView inside onCreate() method of DirectionActivity.Java and call getMapAsync() to register the callback.
Java:
private HuaweiMap hMap;
private MapView mMapView;mMapView = findViewById(R.id.mapView);
Bundle mapViewBundle = null;
if (savedInstanceState != null) {
mapViewBundle = savedInstanceState.getBundle(MAP_BUNDLE_KEY);
}
mMapView.onCreate(mapViewBundle);
mMapView.getMapAsync(this);
Step 5: Inside onMapReady() callback, get the Huawei Map object and set the current location enabled.
Java:
public void onMapReady(HuaweiMap huaweiMap) {
Log.d(TAG, "onMapReady: ");
hMap = huaweiMap;
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { return;
}
hMap.setMyLocationEnabled(true);
CameraPosition build = new CameraPosition.Builder().target(new LatLng(20.5937, 78.9629)).zoom(4).build();
CameraUpdate cameraUpdate = CameraUpdateFactory.newCameraPosition(build);
hMap.animateCamera(cameraUpdate);
}
Step 6: Override the onStart(), onStop(),onDestroy(),onPause(), onResume() and onLowMemory() in the DirectionActivity.Java.
Java:
@Override
protected void onStart() {
super.onStart();
mMapView.onStart();
}
@Override
protected void onStop() {
super.onStop();
mMapView.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
mMapView.onDestroy();
}
@Override
protected void onPause() {
mMapView.onPause();
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
mMapView.onResume();
}
@Override
public void onLowMemory() {
super.onLowMemory();
mMapView.onLowMemory();
}
Now integration of map done.
Result:
Part 3: Location Kit Integration
This kit is being used for getting the current location of the user.
Step 1: Initialize the current location instances.
Java:
private LocationCallback mLocationCallback;
private LocationRequest mLocationRequest;
private FusedLocationProviderClient fusedLocationProviderClient;
private SettingsClient settingsClient;private double latitude;
private double longitude;
Step 2: call getCurrentLocation() inside onCreate() method of DirectionActivity.Java and save it.
Java:
private void getCurrentLocation(){
//create a fusedLocationProviderClient
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
//create a settingsClient
settingsClient = LocationServices.getSettingsClient(this);
mLocationRequest = new LocationRequest();
// set the interval for location updates, in milliseconds.
mLocationRequest.setInterval(10000);
// set the priority of the request
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
if (null == mLocationCallback) {
mLocationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
if (locationResult != null) {
List<Location> locations = locationResult.getLocations();
if (!locations.isEmpty()) {
Location loc = locations.get(0);
latitude = loc.getLatitude();
longitude = loc.getLongitude();
if(count == 0){
count = count + 1;
getRoutes();
}
}
}
}
@Override
public void onLocationAvailability(LocationAvailability locationAvailability) {
if (locationAvailability != null) {
boolean flag = locationAvailability.isLocationAvailable();
Toast.makeText(DirectionActivity.this, "isLocationAvailable:"+flag, Toast.LENGTH_SHORT).show();
}
}
};
}
}
Step 3: call requestLocationUpdatesWithCallback() method after getCurrentLocation()insideonCreate().
Java:
private void requestLocationUpdatesWithCallback() {
try {
LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
builder.addLocationRequest(mLocationRequest);
LocationSettingsRequest locationSettingsRequest = builder.build();
// check devices settings before request location updates.
settingsClient.checkLocationSettings(locationSettingsRequest)
.addOnSuccessListener(new OnSuccessListener<LocationSettingsResponse>()
{
@Override
public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
Log.i(TAG, "check location settings success");
// request location updates
fusedLocationProviderClient
.requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.getMainLooper())
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Toast.makeText(DirectionActivity.this,"requestLocationUpdatesWithCallback onFailure:",Toast.LENGTH_SHORT).show();
}
});
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e)
{
Toast.makeText(DirectionActivity.this,"checkLocationSetting onFailure:",Toast.LENGTH_SHORT).show();
int statusCode = ((ApiException) e).getStatusCode();
switch (statusCode) {
case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
try {
ResolvableApiException rae = (ResolvableApiException) e;
rae.startResolutionForResult(DirectionActivity.this, 0);
} catch (IntentSender.SendIntentException sie) {
Log.e(TAG, "PendingIntent unable to execute request.");
}
break;
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
Now getting current location part completed.
Part 4: Direction API Implementation
This API is getting used for getting the routes between source and destination location.
Step 1: Get the destination location from place info and save it inside onCreate()method of DirectionActivity.java.
Java:
// Destination location data
private double destLatitude;
private double destLongitude;destLatitude = getIntent().getDoubleExtra("latitude",0.0);
destLongitude = getIntent().getDoubleExtra("longitude",0.0);
Step 2: Create DirectionService.java for getting the routes between source and destination location.
Java:
public class DirectionService {
public static final String ROOT_URL = "https://mapapi.cloud.huawei.com/mapApi/v1/routeService/";
public static final String conection = "?key=";
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
final MutableLiveData<JsonData> jsonData = new MutableLiveData<>();
private String jsonResponse;
public RouteInfo info;
private static DirectionService directionService;
public static DirectionService getInstance(){
if (directionService == null)
directionService = new DirectionService();
return directionService;
}
public void setRouteInfo(RouteInfo info)
{
this.info = info;
}
public void driving(String serviceName, String apiKey, Route route) throws UnsupportedEncodingException {
JSONObject json = new JSONObject();
JSONObject origin = new JSONObject();
JSONObject destination = new JSONObject();
try {
origin.put("lng",route.getOrigin().getLng());
origin.put("lat", route.getOrigin().getLat());
destination.put("lng", route.getDestination().getLng());
destination.put("lat", route.getDestination().getLat());
json.put("origin", origin);
json.put("destination", destination);
} catch (JSONException e) {
Log.e("error", e.getMessage());
}
RequestBody body = RequestBody.create(JSON, String.valueOf(json));
OkHttpClient client = new OkHttpClient();
Request request =
new Request.Builder().url(ROOT_URL + serviceName + conection + URLEncoder.encode(apiKey, "UTF-8"))
.post(body)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e("driving", e.toString());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
// Log.d("driving", response.body().string());
info.routeInfo(response.body().string());
}
});
}
}
Step 3: Call the getRoute() method inside getCurrentLocation() of DirectionActivity.Java.
Java:
private void getRoutes()
{
// get the routes
Origin origin = new Origin();
origin.setLat(latitude);
origin.setLng(longitude);
Destination dest = new Destination();
dest.setLat(destLatitude);
dest.setLng(destLongitude);
Route route = new Route();
route.setOrigin(origin);
route.setDestination(dest);
try {
DirectionService.getInstance().setRouteInfo(this);
DirectionService.getInstance().driving("driving",MainActivity.MY_API_KEY,route);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
Step 4: Create an Interface RouteInfo.
Java:
public interface RouteInfo {
void routeInfo(String info);
}
Step 5: DirectionActivity.Java will implement RouteInfo interface.
Java:
public class DirectionActivity extends AppCompatActivity implements OnMapReadyCallback,RouteInfo{
}
Step 6: Override routeInfo() method and convert string response to Json object using gson library.
Java:
@Override
public void routeInfo(String info) {
Gson gson = new Gson();
JsonData obj = gson.fromJson(info,JsonData.class);
addPolyline(obj);
addMarker();
animateCameraToCurrentLocation();
}
Step 7: Add polyline from the routes info.
Java:
private void addPolyline(JsonData obj) {
if(hMap == null){
return;
}
if (null != mPolyline) {
mPolyline.remove();
mPolyline = null;
}
PolylineOptions options = new PolylineOptions();
if(obj != null){
ArrayList<Routes> routes = obj.getRoutes();
if(routes != null && routes.size() > 0){
ArrayList<Path> paths = routes.get(0).getPaths();
if(paths != null && paths.size() > 0){
ArrayList<Step> steps = paths.get(0).getSteps();
if(steps != null && steps.size() > 0)
{
for(Step step : steps) {
ArrayList<com.huawei.sitekitsampleapp.model.Polyline> polylines = step.getPolyline();
if(polylines != null && polylines.size() > 0){
for(com.huawei.sitekitsampleapp.model.Polyline polyline : polylines){
// Add lat lng to options
options.add(new LatLng(polyline.getLat(),polyline.getLng()));
}
}
}
}
}
}
}
options.color(Color.GREEN).width(3);
mPolyline = hMap.addPolyline(options);
}
Step 8: Add a Marker at destination location.
Java:
private void addMarker() {
if (null != mMarker) {
mMarker.remove();
}
MarkerOptions options = new MarkerOptions()
.position(new LatLng(destLatitude, destLongitude)).icon(BitmapDescriptorFactory.fromResource(R.drawable.marker));
mMarker = hMap.addMarker(options);
}
Step 9: Add marker.xml to drawable folder.
XML:
<vector android:height="24dp" android:tint="#FF1730"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
</vector>
Step 10: Animate camera to current location.
Java:
private void animateCameraToCurrentLocation()
{
CameraPosition build = new CameraPosition.Builder().target(new LatLng(latitude, longitude)).zoom(13).build();
CameraUpdate cameraUpdate = CameraUpdateFactory.newCameraPosition(build);
hMap.animateCamera(cameraUpdate);
}
Now showing the direction between two place done.
Result:
Tips and Tricks:
Set input properly for origin and Destination for getting the routes.
Conclusion:
This application can help to show the direction between your current location to your favorite place. You can get the restaurants, schools and places and navigate to the same.
Reference:
https://developer.huawei.com/consumer/en/doc/development/HMSCore-References-V5/directions-walking-0000001050161494-V5#EN-US_TOPIC_0000001050161494__section12176172981317
https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/android-sdk-keyword-search-0000001050156630
https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/android-sdk-map-instance-creation-0000001062881706
https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/location-develop-steps-0000001050746143
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Article Introduction
In this article, we will develop Music Station android app for Huawei Vision S (Smart TV) devices. Huawei Vision S is a brand new large screen category and important part of Huawei's "1+8+N" full-scenario services and Huawei Developer Ecosystem. Since, Huawei Vision S system architecture supports AOSP project framework, we used Leanback Library which offers extensive features for large screens to develop our user experience.
Why an app for Huawei Vision S?
Large screen offers better visibility and enhanced user experience. Due to Covid-19 lockdown, Smart TV has grown to include over 80% more users than it had this time last year. Total distribution of usage for TV is increasing rapidly. As a result of this, total number of TV apps has jumped dramatically including educational and entertainment apps.
Designing App for Huawei Vision S
While designing an app for Huawei Vision S, we have to keep following key points in our mind:
Build Layout for TV: We must design landscape orientation layout that allows users to easily see the screen 10 feet away from the TV.
Management Controller: Our app must support arrow keys and handle offline controllers as well as inputs from multiple controllers.
For this article, we implemented Leanback Library which offers amazing and interactive user experience for apps such as Audio/Video players and so on.
Pre-Requisites
Before getting started, following are the requirements:
Android Studio (During this tutorial, we used version 4.1.1)
Android SDK 24 or later
Huawei Vision S for testing
Development
Following are the major steps of development for this article:
Step 1: Add Dependencies & Permissions
1.1: Add the following dependencies in the app level build.gradle file:
Java:
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
// TV Libs
implementation 'androidx.leanback:leanback:1.0.0'
implementation 'androidx.leanback:leanback-preference:1.0.0'
// General
implementation 'org.greenrobot:eventbus:3.2.0'
implementation 'com.google.code.gson:gson:2.8.7'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.nabinbhandari.android:permissions:3.8'
// Animation
implementation 'com.airbnb.android:lottie:3.7.0'
implementation 'com.gauravk.audiovisualizer:audiovisualizer:0.9.2'
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation ('com.github.bumptech.glide:okhttp3-integration:4.12.0'){
exclude group: 'glide-parent'
}
}
1.2: We are developing this app only for TV. So, we will disable the Touch_Screen requirements and enable the Leanback. For the audio visualizer, we need Record_Audio permission. Add the following permissions and features tag in the AndroidManifest.xml:
Java:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-feature
android:name="android.software.leanback"
android:required="true" />
<uses-feature
android:name="android.hardware.microphone"
android:required="false" />
1.3: Huawei Vision S supports Leanback Library but does not supports Leanback Launcher. So, we added the following tag in the SplashActivity inside the AndroidManifest.xml:
Code:
<activity android:name=".activities.SplashActivity"
android:screenOrientation="landscape"
android:banner="@drawable/app_icon"
android:icon="@drawable/app_icon"
android:label="@string/app_name"
android:launchMode="singleTop"
android:logo="@drawable/app_icon"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<!-- HUAWEI Vision S only support CATEGORY_LAUNCHER -->
<category android:name="android.intent.category.LAUNCHER" />
<!-- ADD the below line only if you want to release the same code on Google Play -->
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>
1.4: The banner icon is required when we are developing apps for TV. The size for Huawei Vision S banner icon is 496x280 which must be added in the drawable folder.
Step 2: Generating Supported Language JSON
Since our main goal is playing Music, we restricted data source and generated a JSON file locally to avoid API creation and API calling. In real world scenario, an API can be developed or can be used to get the real-time data.
Step 3: Building Layout
3.1: The most important layout of our application is of PlayerActivity. Add the following activity_player.xml layout file in the layout folder of the res. We developed this layout to enhance user experience and add custom views like Audio Visualizer. The layout has two main sub-layouts, Player Content View and Error View.
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:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/main_background"
android:keepScreenOn="true">
<ImageView
android:id="@+id/imgBackground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/main_background" />
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/splashAnimation"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:lottie_autoPlay="false"
app:lottie_progress="53"
app:lottie_rawRes="@raw/splash_animation" />
<com.gauravk.audiovisualizer.visualizer.CircleLineVisualizer
android:id="@+id/blastVisualizer"
android:layout_width="match_parent"
android:layout_height="match_parent"
custom:avColor="@color/light_red"
custom:avDensity="0.8"
custom:avSpeed="normal"
custom:avType="fill" />
<ImageView
android:id="@+id/imgOverlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0.5"
android:background="@android:color/black" />
<ProgressBar
android:id="@+id/progressBarLoader"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerInParent="true"
android:indeterminate="true"
android:indeterminateTint="@color/colorPrimaryDark"
android:indeterminateTintMode="src_atop"
android:visibility="gone" />
<RelativeLayout
android:id="@+id/rlContentLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:paddingStart="50dp"
android:paddingEnd="50dp">
<ImageView
android:id="@+id/imgIcon"
android:layout_width="80dp"
android:layout_height="80dp" />
<TextView
android:id="@+id/txtSongName"
style="@style/TextViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/imgIcon"
android:layout_marginTop="5dp"
android:textSize="@dimen/title_text_size" />
<TextView
android:id="@+id/txtArtistName"
style="@style/TextViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/txtSongName"
android:layout_marginTop="5dp"
android:textSize="@dimen/artist_text_size" />
<TextView
android:id="@+id/txtCategoryName"
style="@style/TextViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/txtArtistName"
android:layout_marginTop="5dp"
android:textSize="@dimen/category_text_size" />
<ImageButton
android:id="@+id/btnPlayPause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/txtCategoryName"
android:layout_marginTop="50dp"
android:background="@drawable/ui_selector_bg"
android:focusable="true"
android:src="@drawable/lb_ic_play" />
<SeekBar
android:id="@+id/seekBarAudio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/btnPlayPause"
android:layout_marginTop="10dp"
android:background="@drawable/ui_selector_bg"
android:colorControlActivated="@color/white"
android:focusable="true"
android:progressTint="@color/white"
android:thumbTint="@color/white" />
<TextView
android:id="@+id/txtTotalDuration"
style="@style/TextViewStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/seekBarAudio"
android:layout_alignEnd="@+id/seekBarAudio"
android:layout_marginTop="5dp"
android:text=" / -"
android:textSize="@dimen/time_text_size" />
<TextView
android:id="@+id/txtCurrentTime"
style="@style/TextViewStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/seekBarAudio"
android:layout_marginTop="5dp"
android:layout_toStartOf="@+id/txtTotalDuration"
android:text="-"
android:textSize="@dimen/time_text_size" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/rlErrorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background"
android:visibility="gone">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true">
<ImageView
android:id="@+id/imgErrorLoading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:src="@drawable/lb_ic_sad_cloud" />
<TextView
android:id="@+id/txtError"
style="@style/TextViewStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/imgErrorLoading"
android:layout_centerHorizontal="true"
android:text="@string/error_message"
android:textSize="@dimen/time_text_size" />
<Button
android:id="@+id/btnDismiss"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/txtError"
android:layout_centerHorizontal="true"
android:layout_marginTop="30dp"
android:focusable="true"
android:text="@string/dismiss_error" />
</RelativeLayout>
</RelativeLayout>
</RelativeLayout>
3.2: In this article, we used Lottie animation in the SplashActivity for better user experience inside the following activity_splash.xml.
XML:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@color/colorPrimaryDark"
tools:context=".activities.SplashActivity">
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/splashAnimation"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:lottie_autoPlay="true"
app:lottie_repeatCount="1"
app:lottie_rawRes="@raw/splash_animation" />
<ImageView
android:id="@+id/imgAppIcon"
android:src="@drawable/img_music_station_logo"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:layout_marginBottom="40dp"
android:scaleX="0.8"
android:scaleY="0.8"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
Step 4: Adding JAVA Classes
4.1: We extended the MainFragment class from BrowseFragment which is Leanback home layout component. This view handles the landing screen containing interactive side menu, main content area with different cards and navigation between them.
Java:
public class MainFragment extends BrowseFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
prepareBackgroundManager();
setupUIElements();
loadRows();
setupEventListeners();
}
private void loadRows() {
ArrayObjectAdapter rowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
CardPresenter cardPresenter = new CardPresenter();
int i;
for (i = 0; i < DataUtil.getData(getActivity()).size(); i++) {
CategoryModel categoryModel = DataUtil.getData(getActivity()).get(i);
ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter);
for (int j = 0; j < categoryModel.getCategorySongs().size(); j++) {
listRowAdapter.add(categoryModel.getCategorySongs().get(j));
}
HeaderItem header = new HeaderItem(i, categoryModel.getCategoryName());
rowsAdapter.add(new ListRow(header, listRowAdapter));
}
setAdapter(rowsAdapter);
}
private void prepareBackgroundManager() {
BackgroundManager mBackgroundManager = BackgroundManager.getInstance(getActivity());
mBackgroundManager.attach(getActivity().getWindow());
mBackgroundManager.setColor(getResources().getColor(R.color.main_background));
DisplayMetrics mMetrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
}
private void setupUIElements() {
setTitle(getString(R.string.app_name));
setHeadersState(HEADERS_ENABLED);
setHeadersTransitionOnBackEnabled(true);
setBrandColor(ContextCompat.getColor(getActivity(), R.color.colorPrimaryDark));
setSearchAffordanceColor(ContextCompat.getColor(getActivity(), R.color.colorPrimary));
}
private void setupEventListeners() {
setOnItemViewClickedListener(new ItemViewClickedListener());
}
private final class ItemViewClickedListener implements OnItemViewClickedListener {
@Override
public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) {
if (item instanceof Song) {
Song song = (Song) item;
Intent intent = new Intent(getActivity(), PlayerActivity.class);
intent.putExtra(DataUtil.SONG_DETAIL, song);
getActivity().startActivity(intent);
}
}
}
}
4.2: Cards are displayed using CardPresenter which is extended by Presenter from the Leanback library.
Java:
public class CardPresenter extends Presenter {
private static final int CARD_WIDTH = 313;
private static final int CARD_HEIGHT = 176;
private static int sSelectedBackgroundColor;
private static int sDefaultBackgroundColor;
private static void updateCardBackgroundColor(ImageCardView view, boolean selected) {
int color = selected ? sSelectedBackgroundColor : sDefaultBackgroundColor;
view.setBackgroundColor(color);
view.findViewById(R.id.info_field).setBackgroundColor(color);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
sDefaultBackgroundColor = ContextCompat.getColor(parent.getContext(), R.color.background);
sSelectedBackgroundColor = ContextCompat.getColor(parent.getContext(), R.color.colorPrimaryDark);
ImageCardView cardView =
new ImageCardView(parent.getContext()) {
@Override
public void setSelected(boolean selected) {
updateCardBackgroundColor(this, selected);
super.setSelected(selected);
}
};
cardView.setFocusable(true);
cardView.setFocusableInTouchMode(true);
updateCardBackgroundColor(cardView, false);
return new ViewHolder(cardView);
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, Object item) {
Song song = (Song) item;
ImageCardView cardView = (ImageCardView) viewHolder.view;
cardView.setTitleText(song.getSongName());
cardView.setContentText(song.getArtistName());
cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT);
Glide.with(viewHolder.view.getContext())
.load(song.getImageURL())
.centerCrop()
.placeholder(R.drawable.app_icon)
.error(R.drawable.app_icon)
.into(cardView.getMainImageView());
}
@Override
public void onUnbindViewHolder(ViewHolder viewHolder) {
ImageCardView cardView = (ImageCardView) viewHolder.view;
cardView.setBadgeImage(null);
cardView.setMainImage(null);
}
}
4.3: Whenever user click on any Card Item, PlayerActivity is opened. Following are some of the important functions of the PlayerActivity.java. Please refer to the github link for complete code of this class.
Java:
private void handlePlayer(){
if (currentState == MediaPlayerHolder.PlayerState.PLAYING) {
pauseSong();
} else if (currentState == MediaPlayerHolder.PlayerState.PAUSED ||
currentState == MediaPlayerHolder.PlayerState.COMPLETED ||
currentState == MediaPlayerHolder.PlayerState.RESET) {
playSong();
}
}
private void setUI() {
if (song != null) {
txtSongName.setText(song.getSongName());
txtArtistName.setText(song.getArtistName());
txtCategoryName.setText(song.getCategoryName());
Glide.with(this)
.load(song.getImageURL())
.centerCrop()
.circleCrop()
.placeholder(R.drawable.app_icon)
.error(R.drawable.app_icon)
.into(imgIcon);
}
setupSeekBar();
}
private void pauseSong() {
EventBus.getDefault().post(new LocalEventFromMainActivity.PausePlayback());
}
private void playSong() {
EventBus.getDefault().post(new LocalEventFromMainActivity.StartPlayback());
}
private void resetSong() {
EventBus.getDefault().post(new LocalEventFromMainActivity.ResetPlayback());
}
public void log(StringBuffer formattedMessage) {
Log.d(PlayerActivity.class.getSimpleName(), String.format("log: %s", formattedMessage));
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(LocalEventFromMediaPlayerHolder.UpdateLog event) {
log(event.formattedMessage);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(LocalEventFromMediaPlayerHolder.PlaybackDuration event) {
seekBarAudio.setMax(event.duration);
setTotalDuration(event.duration);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(LocalEventFromMediaPlayerHolder.PlaybackPosition event) {
if (!isUserSeeking) {
seekBarAudio.setProgress(event.position, true);
updateProgressTime(event.position);
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(LocalEventFromMediaPlayerHolder.StateChanged event) {
hideLoader();
currentState = event.currentState;
switch (event.currentState) {
case PLAYING:
btnPlayPause.setImageResource(R.drawable.lb_ic_pause);
if (mMediaPlayerHolder.getAudioSessionId() != -1 && blastVisualizer != null){
blastVisualizer.setAudioSessionId(mMediaPlayerHolder.getAudioSessionId());
blastVisualizer.show();
}
break;
case PAUSED:
case RESET:
case COMPLETED:
btnPlayPause.setImageResource(R.drawable.lb_ic_play);
if (blastVisualizer != null){
blastVisualizer.hide();
}
break;
case ERROR:
showError();
break;
}
}
private void showError() {
if(song != null){
String title = song.getSongName();
String artist = song.getArtistName();
String songName = "Unable to play " + title + " (" + artist + ")";
txtError.setText(songName);
}
rlErrorLayout.setVisibility(View.VISIBLE);
btnDismiss.requestFocus();
}
private void setTotalDuration(int duration) {
long totalSecs = TimeUnit.MILLISECONDS.toSeconds(duration);
long hours = totalSecs / 3600;
long minutes = (totalSecs % 3600) / 60;
long seconds = totalSecs % 60;
String totalDuration = String.format(Locale.ENGLISH, "%02d:%02d:%02d", hours, minutes, seconds);
if (hours == 0) {
totalDuration = String.format(Locale.ENGLISH, "%02d:%02d", minutes, seconds);
}
String text = " / " + totalDuration;
txtTotalDuration.setText(text);
}
private void updateProgressTime(int position){
long currentSecs = TimeUnit.MILLISECONDS.toSeconds(position);
long hours = currentSecs / 3600;
long minutes = (currentSecs % 3600) / 60;
long seconds = currentSecs % 60;
String currentDuration = String.format(Locale.ENGLISH, "%02d:%02d:%02d", hours, minutes, seconds);
if (hours == 0) {
currentDuration = String.format(Locale.ENGLISH, "%02d:%02d", minutes, seconds);
}
txtCurrentTime.setText(currentDuration);
}
4.4: We used EventBus to asynchronously notify the PlayerActivity UI changes. The MediaPlayerHolder.java class handles the MediaPlayer states and manages the functionalities like playing, pause and seekbar position updates. Please refer to the github link for complete code of this class.
Java:
public int getAudioSessionId(){
if(mMediaPlayer != null){
return mMediaPlayer.getAudioSessionId();
} else
return -1;
}
public void release() {
logToUI("release() and mMediaPlayer = null");
mMediaPlayer.release();
EventBus.getDefault().unregister(this);
}
public void stop() {
logToUI("stop() and mMediaPlayer = null");
mMediaPlayer.stop();
}
public void play() {
if (!mMediaPlayer.isPlaying()) {
logToUI(String.format("start() %s", urlPath));
mMediaPlayer.start();
startUpdatingSeekbarWithPlaybackProgress();
EventBus.getDefault()
.post(new LocalEventFromMediaPlayerHolder.StateChanged(PlayerState.PLAYING));
}
}
public void pause() {
if (mMediaPlayer.isPlaying()) {
mMediaPlayer.pause();
logToUI("pause()");
EventBus.getDefault()
.post(new LocalEventFromMediaPlayerHolder.StateChanged(PlayerState.PAUSED));
}
}
public void reset() {
logToUI("reset()");
mMediaPlayer.reset();
load(urlPath);
stopUpdatingSeekbarWithPlaybackProgress(true);
EventBus.getDefault()
.post(new LocalEventFromMediaPlayerHolder.StateChanged(PlayerState.RESET));
}
public void load(String url) {
this.urlPath = url.replaceAll(" ", "%20");
if (mMediaPlayer != null) {
try {
mMediaPlayer.setAudioAttributes(
new AudioAttributes
.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build());
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer player) {
initSeekbar();
play();
}
});
mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
EventBus.getDefault()
.post(new LocalEventFromMediaPlayerHolder.StateChanged(PlayerState.ERROR));
return false;
}
});
mMediaPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
@Override
public void onSeekComplete(MediaPlayer arg0) {
EventBus.getDefault().post(new LocalEventFromMediaPlayerHolder.StateChanged(PlayerState.PLAYING));
SystemClock.sleep(200);
mMediaPlayer.start();
}
});
logToUI("load() {1. setDataSource}");
mMediaPlayer.setDataSource(urlPath);
logToUI("load() {2. prepare}");
mMediaPlayer.prepareAsync();
} catch (Exception e) {
EventBus.getDefault().post(new LocalEventFromMediaPlayerHolder.StateChanged(PlayerState.ERROR));
logToUI(e.toString());
}
}
}
public void seekTo(int duration) {
logToUI(String.format(Locale.ENGLISH, "seekTo() %d ms", duration));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
mMediaPlayer.seekTo(duration, MediaPlayer.SEEK_CLOSEST);
else
mMediaPlayer.seekTo(duration);
}
private void stopUpdatingSeekbarWithPlaybackProgress(boolean resetUIPlaybackPosition) {
if (mExecutor != null) {
mExecutor.shutdownNow();
}
mExecutor = null;
mSeekbarProgressUpdateTask = null;
if (resetUIPlaybackPosition) {
EventBus.getDefault().post(new LocalEventFromMediaPlayerHolder.PlaybackPosition(0));
}
}
private void startUpdatingSeekbarWithPlaybackProgress() {
// Setup a recurring task to sync the mMediaPlayer position with the Seekbar.
if (mExecutor == null) {
mExecutor = Executors.newSingleThreadScheduledExecutor();
}
if (mSeekbarProgressUpdateTask == null) {
mSeekbarProgressUpdateTask = new Runnable() {
@Override
public void run() {
if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
int currentPosition = mMediaPlayer.getCurrentPosition();
EventBus.getDefault().post(
new LocalEventFromMediaPlayerHolder.PlaybackPosition(
currentPosition));
}
}
};
}
mExecutor.scheduleAtFixedRate(
mSeekbarProgressUpdateTask,
0,
SEEKBAR_REFRESH_INTERVAL_MS,
TimeUnit.MILLISECONDS
);
}
public void initSeekbar() {
// Set the duration.
final int duration = mMediaPlayer.getDuration();
EventBus.getDefault().post(new LocalEventFromMediaPlayerHolder.PlaybackDuration(duration));
logToUI(String.format(Locale.ENGLISH, "setting seekbar max %d sec", TimeUnit.MILLISECONDS.toSeconds(duration)));
}
When user click Select button on the Remote Control of Huawei Vision S on any item, the player view is opened and MediaPlayer start loading the song url. Once the song is loaded, it starts playing and the icon of Play changes to Pause. The Audio Visualizer takes AudioSessionId to sync with audio song. By default, seekbar is selected for the user to skip the songs using Right and Left buttons of the Remote Control. The duration of the song updates based on seekbar position.
Step 5: Run the application
We have added all the required code. Now, just build the project, run the application and test on Huawei Vision S.
Conclusion
Using Leanback, developers can develop beautifully crafted android applications with amazing UI/UX experience for Huawei Vision S. They can also enhance their user engagement and behavior. Combining different Huawei Kits supported by Vision S like Account or IAP can yield amazing results.
Tips and Tricks
You must use default Launcher if you are developing app for Huawei Vision S.
Leanback Launcher is not supported by Huawei Vision S.
If you have same code base for Mobile and Vision S devices, you can use TVUtils class to check at run-time about the device and offer functionalities based on it.
Make sure to add all the permissions like RECORD_AUDIO, INTERNET.
Make sure to add run-time permissions check. In this article, we used 3rd party Permission Check library with custom Dialog if user deny any of the required permission.
Always use animation libraries like Lottie or ViewAnimator to enhance UI/UX in your application.
We used AudioVisualizer library to bring Music feel on our Player UI.
References
Android TV Documentation:
https://developer.android.com/training/tv/start
https://developer.android.com/training/tv/start/layouts
https://developer.android.com/training/tv/start/controllers
https://developer.android.com/training/tv/start/navigation
Lottie Android Documentation:
http://airbnb.io/lottie/#/android
Github Code Link:
https://github.com/yasirtahir/MusicStationTV
Original Source