Hey guys,
I am trying to change textview properties of a textview that looks like this:
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
In a seperate Activity that looks like this:
I tried to do this with bundles but I can't get it to work.
This is how my BookActivity looks like:
Code:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.book_activity);
//this is where the size property comes in
Integer size = getIntent().getExtras().getInt("SGRkey");
TextView test2 = (TextView) findViewById(R.id.booktext);
test2.setTextSize(size);
Spinner spinner = (Spinner) findViewById(R.id.kapitelspinner);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
this, R.array.kapitel_array, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(new MyOnItemSelectedListener());
}
public class MyOnItemSelectedListener implements OnItemSelectedListener {
public void onItemSelected(AdapterView<?> parent,
View view, int pos, long id) {
Toast.makeText(parent.getContext(),
parent.getItemAtPosition(pos).toString(), Toast.LENGTH_LONG).show();
final String[] theporn = getResources().getStringArray(R.array.allkapitel);
TextView text = (TextView) findViewById(R.id.booktext);
text.setText(theporn[pos]);
}
public void onNothingSelected(AdapterView parent) {
// Do nothing.
}
(i pick the chapter string in the spinner and that works just fine.)
And this is how my SettingsActivity looks like:
Code:
public class SettingsActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.settings_view);
Spinner SGRspinner = (Spinner) findViewById(R.id.schriftgroeße_spinner);
ArrayAdapter<CharSequence> SGRadapter = ArrayAdapter.createFromResource(
this, R.array.schriftgroesse_list, android.R.layout.simple_spinner_item);
SGRadapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
SGRspinner.setAdapter(SGRadapter);
}
public class SGROnItemSelectedListener implements OnItemSelectedListener {
public void onItemSelected(AdapterView<?> parent,
View view, int pos, long id) {
Intent answer = new Intent();
Toast.makeText(parent.getContext(),
parent.getItemAtPosition(pos).toString(), Toast.LENGTH_LONG).show();
final String[] SGRstring = getResources().getStringArray(R.array.schriftgroesse_list);
int SGRint = Integer.parseInt(SGRstring[pos]);
Bundle size = new Bundle();
size.putInt("SGRkey", SGRint);
Intent nextActivity = new Intent(com.asm.reader.SettingsActivity.this, com.asm.reader.BookActivity.class);
nextActivity.putExtras(size);
com.asm.reader.SettingsActivity.this.startActivity(nextActivity);
}
public void onNothingSelected(AdapterView parent) {
// Do nothing.
}
}
I get an error when I try this. All activities are declared in the manifest. I really don't know how to go on. I'm pretty new to this, so sorry if this is something simple, but any help would be greatly appreciated!!
What is the error and where does it occur? Have you tried stepping through with the debugger?
I think it would be better if you'd do your layout on xml.
Sent from my SGH-T959 using XDA App
Try using a PreferenceActivity instead of an Activity for your settings. Then tie in SharedPreferences so that a value there can be edited and displayed by two activities. One will write the value and the other will read it
also... your code seems to have a bunch of useless stuff. in your SGROnItemSelectedListener you have an Intent you never use Intent answer = new Intent();
I don't see an onRecieve() to do anything with the intent you sent
From something awesome
first of all, thanks for the replies!
when I run the application, it says it has stopped unexpectedly.
the debugger says this:
@iynfynity
I have my layout in xml. im trying to change it with the settings activity
@killersnowman
the google dev guide says that prior to honeycomb, the class only allows a single preference. do you think that would interfere with what i wanna do? (since i wanna make the app available for API level 4 or so. thanks for the advice!
I'm trying to figure out the flow of things here.
So I'm assuming that your app launches and you get the tab bar shown and "Home" is the default activity (and its code is the first code block) and it starts normally. Now you hit the "Settings" tab and its activity (and its code is in the second block) starts OK, then you "Select Max Size" (assuming my rusty German is correct), set it to 8, and you want this "size" to be used to change the font size (guessing here) of the textview in the "Home" activity?
If so, then I don't think you are doing this right. The code in the settings tab's activity should not try to relaunch the home activity. You should save the settings value somewhere (like a preference class) then when the Home activity is reactivated (by the user clicking back to that tab) you should read the preferences and adjust your textview using the saved value.
Gene Poole said:
If so, then I don't think you are doing this right. The code in the settings tab's activity should not try to relaunch the home activity. You should save the settings value somewhere (like a preference class) then when the Home activity is reactivated (by the user clicking back to that tab) you should read the preferences and adjust your textview using the saved value.
Click to expand...
Click to collapse
yep. thats what i was trying to say so inelegantly on my mobile
Gene Poole said:
I'm trying to figure out the flow of things here.
So I'm assuming that your app launches and you get the tab bar shown and "Home" is the default activity (and its code is the first code block) and it starts normally. Now you hit the "Settings" tab and its activity (and its code is in the second block) starts OK, then you "Select Max Size" (assuming my rusty German is correct), set it to 8, and you want this "size" to be used to change the font size (guessing here) of the textview in the "Home" activity?
thats exactly how i want it
If so, then I don't think you are doing this right. The code in the settings tab's activity should not try to relaunch the home activity. You should save the settings value somewhere (like a preference class) then when the Home activity is reactivated (by the user clicking back to that tab) you should read the preferences and adjust your textview using the saved value.
thank you very much!
Click to expand...
Click to collapse
I will try it out as soon as i get home from work tonight
I am really puzzled by the coding part of this.
The dev reference looks convoluted and it gets me really confused. I'm sorry that I am such a newbie at this but I couldn't find any good guides out there that helped me. All I got so far is the XML part of it:
Code:
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="Einstellungen am Text">
<ListPreference
android:title="Schriftgröße"
android:summary="Passen Sie die Schriftgröße ihres Textes an"
android:key="schriftgroesse"
android:defaultValue="8"
android:entries="@array/schriftgroesse_list"
android:entryValues="@array/schriftgroesse_values" />
<ListPreference
android:title="Schriftfarbe"
android:summary="Passen Sie die Farbe ihres Textes an"
android:key="schriftfarbe"
android:defaultValue="#ffffff"
android:entries="@array/schriftfarbe_list"
android:entryValues="@array/schriftfarbe_values" />
<ListPreference
android:title="Hintergrundfarbe"
android:summary="Passen Sie den Hintergrund an"
android:key="schriftfarbe"
android:defaultValue="#000000"
android:entries="@array/hintergrundfarbe_list"
android:entryValues="@array/hintergrundfarbe_values" />
</PreferenceCategory>
</PreferenceScreen>
(I know, nothing really..)
It would be very much appreciated if someone could help me !
I am stuck on the color setting
Code:
@Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
TextView text = (TextView) findViewById(R.id.booktext);
SharedPreferences PRFSize=PreferenceManager.getDefaultSharedPreferences(this);
String Size = PRFSize.getString("schriftgroesse", "8");
SharedPreferences PRFColor=PreferenceManager.getDefaultSharedPreferences(this);
String Color = PRFColor.getString("schriftfarbe", "#ffffff");
Integer SizeInt = Integer.parseInt(Size);
text.setTextSize(SizeInt);
//now what?
text.setTextColor();
Context context = getApplicationContext();
CharSequence hello = Size;
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, hello, duration);
toast.show();
}
How do I convert the String (Color) to an interger/float to get it to work for the method setTextColor?? I always get an error when I try to do
Code:
Integer.parseInt( string );
..
I am puzzled
Why not load/save to SharedPreferences as an integer (use Editor.putInt() and getInt() )?
Okay, sorry for the quadriple post, but I got it all now. Thanks for all the help!
Related
I am having a problem with my first Android app. I am posting here looking for guidance to find a solution, or to decide if maybe what I am attempting is to bothersome for someone just learning the android platform. My Java and OO are very strong though.
I am writing a calculator app as my first android app.
I have a mode button on the calculator that changes the buttons to make new functions available.
When mode switches, certain buttons get changed to a different drawable with different colors, and all the text gets updated on all the buttons.
Each mode has a new string array corresponding to the text for that mode.
What I am currently trying to achieve is having a delay between the time that each button is updated, a sort of cascade effect. I have not successfully implemented this.
In the following code I have a toggleLabels method which attempts to do this. It has three if blocks corresponding to the three modes, which each gets a new set of text and buttons. There are two nested for loops in each running i=width and j=height.
To create the cascade effect I grabbed system time, and then subtracted from current time to see that 100ms had passed.
No cascade effect takes place, and when I hit the mode button, there is a long delay then all of the buttons switch at once. The delay seems to be exactly 2 seconds, which corresponds to the cumulative delay of 100ms per button X 20 buttons.
I also tried an alternate method of Thread.Sleep() which yielded the same result, and which I read was bad practice to implement in your UI thread.
Basically I am doing..
loop through each button
{
Change button drawable
Change button text
delay X time
}
But what is showing in the app is
loop through each button
{
Change button drawable
Change button text
}
delay (X * N buttons) amount of time
draw all the buttons at once
I
After doing a lot of investigating, and not finding to much relevant info out there, I am thinking I need to implement a new thread to do this in.
I also came up with the idea of extending a new class of button and overriding the ondraw method with a delay in it. Not sure this is a horribly improper way of doing it.
My code is as follows....
Code:
package com.example.calculator;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
public class MainActivity extends Activity {
EditText displayUpper;
EditText displayLower;
//View root;
LinearLayout keyPanel;
Button[][] buttons;
int width;
int height;
String[] labels1;
String[] labels2;
String[] labels3;
int mode;
long time;
boolean click;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
width=4;
height=5;
mode=3;
click=false;
// root = (LinearLayout)findViewById(R.id.root);
keyPanel = (LinearLayout)findViewById(R.id.keyPanel);
displayUpper = (EditText)findViewById(R.id.editText1);
displayLower = (EditText)findViewById(R.id.editText1);
labels1=getResources().getStringArray(R.array.first_panel);
labels2=getResources().getStringArray(R.array.second_panel);
labels3=getResources().getStringArray(R.array.third_panel);
assignButtons(); //<-must come before toggleLabel
toggleLabel(); //<-must come after assignButtons
final Runnable r = new Runnable()
{
public void run()
{
toggleLabel();
}
};
buttons[3][0].setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Perform action on click
r.run();
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
//---This method stores all the buttons in a multi-dimensional array
//--- Assigns buttons starting at left column from top to bottom
private void assignButtons()
{
buttons=new Button[width][height];
LinearLayout temp;
for(int i=0;i<width;i++)
{
temp = (LinearLayout)keyPanel.getChildAt(i);
for(int j=0;j<height;j++)
{
buttons[i][j] = (Button)temp.getChildAt(j);
}
}
}
//--This method set and toggles all the button labels
private void toggleLabel()
{
int pos=0;
boolean notchange=true;
if(mode==0)//<--If mode=0 change to second set of buttons
{
mode=1;
time=System.currentTimeMillis();
for(int i=0;i<width;i++)
{
for(int j=0;j<height;j++)
{
notchange=true;
while(notchange)
{
if(System.currentTimeMillis()-time>100)
{
notchange=false;
buttons[i][j].setText(labels2[pos]);
pos++;
if(i==3||j==0)
{
buttons[i][j].setBackgroundResource(R.drawable.sec_button);
}
time=System.currentTimeMillis();
}
}
}
}
}
else if(mode==1)//<--If mode=1 change to third set of buttons
{
mode=2;
time=System.currentTimeMillis();
for(int i=0;i<width;i++)
{
for(int j=0;j<height;j++)
{
notchange=true;
while(notchange)
{
if(System.currentTimeMillis()-time>100)
{
notchange=false;
buttons[i][j].setText(labels3[pos]);
pos++;
if(i==3||j==0)
{
buttons[i][j].setBackgroundResource(R.drawable.third_button);
}
time=System.currentTimeMillis();
}
}
}
}
}
else//<--else change back to first set of buttons
{
mode=0;
time=System.currentTimeMillis();
for(int i=0;i<width;i++)
{
for(int j=0;j<height;j++)
{
notchange=true;
while(notchange)
{
if(System.currentTimeMillis()-time>100)
{
notchange=false;
buttons[i][j].setText(labels1[pos]);
pos++;
if(i==3||j==0)
{
buttons[i][j].setBackgroundResource(R.drawable.calcbutton);
}
time=System.currentTimeMillis();
}
}
}
}
}
}
}
Does anyone have any input on this?
More articles like this, you can visit HUAWEI Developer Forum and Medium.
Previously on All About Maps: Episode 1:
The principles of clean architecture
The importance of eliminating map provider dependencies with abstraction
Drawing polylines and markers on Mapbox Maps, Google Maps (GMS), and Huawei Maps (HMS)
Episode 2: Bounded Regions
Welcome to the second episode of AllAboutMaps. In order to understand this blog post better, I would first suggest reading the Episode 1. Otherwise, it will be difficult to follow the context.
In this episode we will talk about bounded regions:
The GPX parser datasource will parse the the file to get the list of attraction points (waypoints in this case).
The datasource module will emit the bounded region information in every 3 seconds
A rectangular bounded area from the centered attraction points with a given radius using a utility method (No dependency to any Map Provider!)
We will move the map camera to the bounded region each time a new bounded region is emitted.
{
"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"
}
ChangeLog since Episode 1
As we all know, software development is continous process. It helps a lot when you have reviewers who can comment on your code and point out issues or come up with suggestions. Since this project is one person task, it is not always easy to spot the flows in the code duirng implementation. The software gets better and evolves hopefully in a good way when we add new features. Once again. I would like to add the disclaimer that my suggestions here are not silver bullets. There are always better approaches. I am more than happy to hear your suggestions in the comments!
You can see the full code change between episode 1 and 2 here:
https://github.com/ulusoyca/AllAboutMaps/compare/episode_1-parse-gpx...episode_2-bounded-region
Here are the main changes I would like to mention:
1- Added MapLifecycleHandlerFragment.kt base class
In episode 1, I had one feature: show the polyline and markers on the map. The base class of all 3 fragments (RouteInfoMapboxFragment, RouteInfoGoogleFragment and RouteInfoHuaweiFragment) called these lifecycle methods. When I added another feature (showing bounded regions) I realized that the new base class of this feature again implemented the same lifecycle methods. This is against the DRY rule (Dont Repeat Yourself)! Here is the base class I introduced so that each feature's base class will extend this one:
Code:
/**
* The base fragment handles map lifecycle. To use it, the mapview classes should implement
* [AllAboutMapView] interface.
*/
abstract class MapLifecycleHandlerFragment : DaggerFragment() {
protected lateinit var mapView: AllAboutMapView
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
mapView.onMapViewCreate(savedInstanceState)
}
override fun onResume() {
super.onResume()
mapView.onMapViewResume()
}
override fun onPause() {
super.onPause()
mapView.onMapViewPause()
}
override fun onStart() {
super.onStart()
mapView.onMapViewStart()
}
override fun onStop() {
super.onStop()
mapView.onMapViewStop()
}
override fun onDestroyView() {
super.onDestroyView()
mapView.onMapViewDestroy()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mapView.onMapViewSaveInstanceState(outState)
}
}
Let's see the big picture now:
2- Refactored the abstraction for styles, marker options, and line options.
In the first episode, we encapsulated a dark map style inside each custom MapView. When I intended to use outdoor map style for the second episode, I realized that my first approach was a mistake. A specific style should not be encapsulated inside MapView. Each feature should be able to select different style. I took the responsibility to load the style from MapViews to fragments. Once the style is loaded, the style object is passed to MapView.
Code:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
mapView = binding.mapView
super.onViewCreated(view, savedInstanceState)
binding.mapView.getMapAsync { mapboxMap ->
binding.mapView.onMapReady(mapboxMap)
mapboxMap.setStyle(Style.OUTDOORS) {
binding.mapView.onStyleLoaded(it)
onMapStyleLoaded()
}
}
}
I also realized the need for MarkerOptions and LineOptions entities in our domain module:
Code:
data class MarkerOptions(
var latLng: LatLng,
var text: String? = null,
@DrawableRes var iconResId: Int,
var iconMapStyleId: String,
@ColorRes var iconColor: Int,
@ColorRes var textColor: Int
)
Code:
data class LineOptions(
var latLngs: List<LatLng>,
@DimenRes var lineWidth: Int,
@ColorRes var lineColor: Int
)
Above entities has properties based on the needs of my project. I only care about the color, text, location, and icon properties of the marker. For polyline, I will customize width, color and text properties. If your project needs to customize the marker offset, opacity, line join type, and other properties, then feel free to add them in your case.
These entities are mapped to corresponding map provider classes:
LineOptions:
Code:
private fun LineOptions.toGoogleLineOptions(context: Context) = PolylineOptions()
.color(ContextCompat.getColor(context, lineColor))
.width(resources.getDimension(lineWidth))
.addAll(latLngs.map { it.toGoogleLatLng() })
Code:
private fun LineOptions.toHuaweiLineOptions(context: Context) = PolylineOptions()
.color(ContextCompat.getColor(context, lineColor))
.width(resources.getDimension(lineWidth))
.addAll(latLngs.map { it.toHuaweiLatLng() })
Code:
private fun LineOptions.toMapboxLineOptions(context: Context): MapboxLineOptions {
val color = ColorUtils.colorToRgbaString(ContextCompat.getColor(context, lineColor))
return MapboxLineOptions()
.withLineColor(color)
.withLineWidth(resources.getDimension(lineWidth))
.withLatLngs(latLngs.map { it.toMapboxLatLng() })
}
MarkerOptions
Code:
private fun DomainMarkerOptions.toGoogleMarkerOptions(): GoogleMarkerOptions {
var markerOptions = GoogleMarkerOptions()
.icon(BitmapDescriptorFactory.fromResource(iconResId))
.position(latLng.toGoogleLatLng())
markerOptions = text?.let { markerOptions.title(it) } ?: markerOptions
return markerOptions
}
Code:
private fun DomainMarkerOptions.toHuaweiMarkerOptions(context: Context): HuaweiMarkerOptions {
BitmapDescriptorFactory.setContext(context)
var markerOptions = HuaweiMarkerOptions()
.icon(BitmapDescriptorFactory.fromResource(iconResId))
.position(latLng.toHuaweiLatLng())
markerOptions = text?.let { markerOptions.title(it) } ?: markerOptions
return markerOptions
}
Code:
private fun DomainMarkerOptions.toMapboxSymbolOptions(context: Context, style: Style): SymbolOptions {
val drawable = ContextCompat.getDrawable(context, iconResId)
val bitmap = BitmapUtils.getBitmapFromDrawable(drawable)!!
style.addImage(iconMapStyleId, bitmap)
val iconColor = ColorUtils.colorToRgbaString(ContextCompat.getColor(context, iconColor))
val textColor = ColorUtils.colorToRgbaString(ContextCompat.getColor(context, textColor))
var symbolOptions = SymbolOptions()
.withIconImage(iconMapStyleId)
.withLatLng(latLng.toMapboxLatLng())
.withIconColor(iconColor)
.withTextColor(textColor)
symbolOptions = text?.let { symbolOptions.withTextField(it) } ?: symbolOptions
return symbolOptions
}
There are minor technical details to handle the differences between map provider APIs but it is out of this blog post's scope.
Earlier our methods for drawing polyline and marker looked like this:
Code:
fun drawPolyline(latLngs: List<LatLng>, @ColorRes mapLineColor: Int)
fun drawMarker(latLng: LatLng, icon: Bitmap, name: String?)
After this refactor they look like this:
Code:
fun drawPolyline(lineOptions: LineOptions)
fun drawMarker(markerOptions: MarkerOptions)
It is a code smell when the number of the arguments in a method increases when you add a new feature. That's why we created data holders to pass around.
3- A secondary constructor method for LatLng
While working on this feature, I realized that a secondary method that constructs the LatLng entity from double values would also be useful when mapping the entities with different map providers. I mentioned the reason why I use inline classes for Latitude and Longitude in the first episode.
Code:
inline class Latitude(val value: Float)
inline class Longitude(val value: Float)
data class LatLng(
val latitude: Latitude,
val longitude: Longitude
) {
constructor(latitude: Double, longitude: Double) : this(
Latitude(latitude.toFloat()),
Longitude(longitude.toFloat())
)
val latDoubleValue: Double
get() = latitude.value.toDouble()
val lngDoubleValue: Double
get() = longitude.value.toDouble()
}
Bounded Region
A bounded region is used to describe a particular area (in many cases it is rectangular) on a map. We usually need two coordinate pairs to describe a region: Soutwest and Northeast. In this stackoverflow answer (https://stackoverflow.com/a/31029389), it is well described:
As expected Mapbox, GMS and HMS maps provide LatLngBounds classes. However, they require a pair of coordinates to construct the bound. In our case we only have one location for each attraction point. We want to show the region with a radius from center on map. We need to do a little bit extra work to calculate the location pair but first let's add LatLngBound entity to our domain module:
Code:
data class LatLngBounds(
val southwestCorner: LatLng,
val northeastCorner: LatLng
)
Implementation
First, let's see the big (literally!) picture:
Thanks to our clean architecture, it is very easy to add a new feature with a new use case. Let's start with the domain module as always:
Code:
/**
* Emits the list of waypoints with a given update interval
*/
class StartWaypointPlaybackUseCase
@Inject constructor(
private val routeInfoRepository: RouteInfoRepository
) {
suspend operator fun invoke(
points: List<Point>,
updateInterval: Long
): Flow<Point> {
return routeInfoRepository.startWaypointPlayback(points, updateInterval)
}
}
The user interacts with the app to start the playback of waypoints. I call this playback because playback is "the reproduction of previously recorded sounds or moving images." We have a list of points to be listened in a given time. We will move map camera periodically from one bounded region to another. The waypoints are emitted from datasource with a given update interval. Domain module doesn't know the implementation details. It sends the request to our datasource module.
Let's see our datasource module. We added a new method in RouteInfoDataRepository:
Code:
override suspend fun startWaypointPlayback(
points: List<Point>,
updateInterval: Long
): Flow<Point> = flow {
val routeInfo = gpxFileDatasource.parseGpxFile()
routeInfo.wayPoints.forEachIndexed { index, waypoint ->
if (index != 0) {
delay(updateInterval)
}
emit(waypoint)
}
}.flowOn(Dispatchers.Default)
Thanks to Kotlin Coroutines, it is very simple to emit the points with a delay. Roman Elizarov describes the flow api in very neat diagram below. If you are interested to learn more about it, his talks are the best to start with.
Long story short, our app module invokes the use case from domain module, domain module forwards the request to datasource module. The corresponding repository class inside datasource module gets the data from GPX datasource and the datasource module orchestrates the data flow.
For full content, you can visit HUAWEI Developer Forum.
More articles like this, you can visit HUAWEI Developer Forum and Medium.
{
"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:
Behavior Awareness is being used to obtain user current behaviour or detect the behavior change.
For calling Behavior Awareness capability we have to assign the given permissions in the manifest file.
Code:
<!-- Behavior detection permission (Android 10 or later). This permission is sensitive and needs to be dynamically applied for in the code after being declared. -->
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<!-- Behavior detection permission (Android 9). This permission is sensitive and needs to be dynamically applied for in the code after being declared. -->
<uses-permission android:name="com.huawei.hms.permission.ACTIVITY_RECOGNITION" />
Capture API
We can use the Capture API to detect user behaviour such as walking, running,cycling,driving etc.
To get current behavior status of a user, we need to call the getBehavior() method - this will return an instance of the BehaviorResponse class that if successful, will contain information about the current user behavior.
Code:
private void getBehavorInfo() {
Awareness.getCaptureClient(this).getBehavior()
.addOnSuccessListener(new OnSuccessListener<BehaviorResponse>() {
@Override
public void onSuccess(BehaviorResponse behaviorResponse) {
BehaviorStatus behaviorStatus = behaviorResponse.getBehaviorStatus();
DetectedBehavior mostLikelyBehavior = behaviorStatus.getMostLikelyBehavior();
String str1 = "Most likely behavior type is " + mostLikelyBehavior.getType() +
",the confidence is " + mostLikelyBehavior.getConfidence();
behavior_info_capture.setTextColor(getColor(R.color.green));
if(mostLikelyBehavior.getType()==0){
behavior_info_capture.setText("You are in Vehicle");
behavior_image.setImageDrawable(getDrawable(R.drawable.vehicle));
}else if(mostLikelyBehavior.getType()==1){
behavior_info_capture.setText("You are in Bicycle");
behavior_image.setImageDrawable(getDrawable(R.drawable.bicycle));
}else if(mostLikelyBehavior.getType()==2){
behavior_info_capture.setText("You are on Foot");
behavior_image.setImageDrawable(getDrawable(R.drawable.foot));
} else if(mostLikelyBehavior.getType()==3){
behavior_info_capture.setText("You are still now");
behavior_image.setImageDrawable(getDrawable(R.drawable.still));
}else if(mostLikelyBehavior.getType()==4){
behavior_info_capture.setText("Unknown behavior");
}else if(mostLikelyBehavior.getType()==7){
behavior_info_capture.setText("You are Walking");
behavior_image.setImageDrawable(getDrawable(R.drawable.walking));
}else if(mostLikelyBehavior.getType()==8){
behavior_info_capture.setText("You are Running");
behavior_image.setImageDrawable(getDrawable(R.drawable.running));
}
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.e(TAG, "get behavior failed", e);
behavior_info_capture.setTextColor(getColor(R.color.red));
behavior_info_capture.setText("get behavior failed: "+e);
}
});
}
Barrier API
We can use the Barrier API to detect the behavior change such as from walking to running.
Public Methods:
Behavior barriers, including the beginning, ending, and keeping barriers of behaviors. Given three mathods which we use in behavior barrier.
public static AwarenessBarrier beginning(int… behaviorTypes)
When a user is in the behavior status, the barrier status is TRUE and a barrier event is reported. After 5s, the barrier status changes to FALSE.
Code:
AwarenessBarrier awarenessBarrier = BehaviorBarrier.beginning(BehaviorBarrier.BEHAVIOR_WALKING);
public static AwarenessBarrier keeping(int… behaviorTypes)
When a user is in the behavior status , the barrier status is TRUE and a barrier event is reported.
Code:
AwarenessBarrier awarenessBarrier = BehaviorBarrier.keeping(BehaviorBarrier.BEHAVIOR_WALKING);
public static AwarenessBarrier ending(int… behaviorTypes)
When a user stops the behavior, the barrier status is TRUE and a barrier event is reported. After 5s, the barrier status changes to FALSE.
Code:
AwarenessBarrier awarenessBarrier = BehaviorBarrier.ending(BehaviorBarrier.BEHAVIOR_WALKING);
Sample Code
Given an example in which a barrier is triggered by the keeping condition. That is, the barrier will be triggered when a user is in stand still.
Code:
public class BehaviorBarrierActivity extends AppCompatActivity {
private static final String TAG ="BehaviorBarrierActivity" ;
private TextView behavior_info_barrier;
private ImageView behavior_info_barrier_image;
PendingIntent pendingIntent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.behavior_barrier_activity);
behavior_info_barrier=findViewById(R.id.behavior_info_barrier);
behavior_info_barrier_image=findViewById(R.id.behavior_info_barrier_image);
// define PendingIntent that will be triggered upon a barrier status change.
final String BARRIER_RECEIVER_ACTION = getApplication().getPackageName() + "BEHAVIOR_BARRIER_RECEIVER_ACTION";
Intent intent = new Intent(BARRIER_RECEIVER_ACTION);
pendingIntent = PendingIntent.getBroadcast(this, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
BehaviorBarrierReceiver barrierReceiver = new BehaviorBarrierReceiver();
registerReceiver(barrierReceiver, new IntentFilter(BARRIER_RECEIVER_ACTION));
addbarrier(this);
}
private void addbarrier(Context context) {
//define the barrier
AwarenessBarrier keepStillBarrier = BehaviorBarrier.keeping(BehaviorBarrier.BEHAVIOR_STILL);
//define the label for the barrier and add the barrier
String behaviorBarrierLabel = "behavior keeping barrier";
//add the barrier
BarrierUpdateRequest.Builder builder = new BarrierUpdateRequest.Builder();
BarrierUpdateRequest request = builder.addBarrier(behaviorBarrierLabel, keepStillBarrier,pendingIntent).build();
Awareness.getBarrierClient(context).updateBarriers(request)
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
Toast.makeText(getApplicationContext(), "add barrier success", Toast.LENGTH_SHORT).show();
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Toast.makeText(getApplicationContext(), "add barrier failed", Toast.LENGTH_SHORT).show();
Log.e(TAG, "add barrier failed", e);
}
});
}
// define the broadcast receiver to listen for the barrier event.
class BehaviorBarrierReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
BarrierStatus barrierStatus = BarrierStatus.extract(intent);
String label = barrierStatus.getBarrierLabel();
switch(barrierStatus.getPresentStatus()) {
case BarrierStatus.TRUE:
Log.i(TAG, label + " status:true");
behavior_info_barrier.setText("You are standing now");
behavior_info_barrier_image.setImageDrawable(getDrawable(R.drawable.still));
break;
case BarrierStatus.FALSE:
Log.i(TAG, label + " status:false");
behavior_info_barrier.setText("Looks like you are not standing");
behavior_info_barrier_image.setImageDrawable(getDrawable(R.drawable.foot));
break;
case BarrierStatus.UNKNOWN:
Log.i(TAG, label + " status:unknown");
break;
}
}
}
}
References:
https://developer.huawei.com/consumer/en/doc/development/HMS-Guides/awareness-behavior-dev
More information like this, you can visit HUAWEI Developer Forum
Original article link: https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0201327669408150034&fid=0101187876626530001
{
"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"
}
After completing part two, which is "); background-size: 1px 1px; background-position: 0px calc(1em + 1px); font-size: 18px; text-decoration: underline;">here, not much left to implement, don’t you worry. Now. we will implement our playlist and a few additional UX-related features about it. Then we will implement advanced playback control buttons to further develop our versatility.
We will be using RecyclerView for the playlist and AudioKit play modes for the advanced playback controls. AudioKit makes it incredibly easy to implement those modes. Also, for viewing the playlist, I will use a bit “unconventional” ways and you can decide how unconventional it is.
If you remember our "); background-size: 1px 1px; background-position: 0px calc(1em + 1px); font-size: 18px; text-decoration: underline;">part 1, I said this:
Now, it is time to explain that code, because we will first implement the playlist feature, before implementing the advanced playback controls.
There I did this:
It first gets my custom adapter called PlaylistAdapter, set the layout manager of my RecyclerView (my playlist), sets the onClickListeners (to choose a song from) and finally calls the super method so that after initializing our managers in the task, let the AsyncTask do the rest that needs to be done.
If you have uncommented here previously, it is time to uncomment now and also let me share and explain the code of PlaylistAdapter, so you will not get ‘undefined’ errors. Create a new Java file for this, as you do for all adapters.
Code:
public class PlaylistAdapter extends RecyclerView.Adapter {
public interface OnItemClickListener {
void onItemClick (List myPlayList, int position);
}
private PlaylistAdapter.OnItemClickListener onItemClickListener;
List myPlayList;
public PlaylistAdapter(List myPlayList){
this.myPlayList = myPlayList;
}
public void setOnItemClickListener(PlaylistAdapter.OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
static class PlayListView extends RecyclerView.ViewHolder {
TextView songNameTextView;
TextView songArtistTextView;
TextView durationTextView;
ImageView moreOptionsImageView;
public PlayListView(View itemView) {
super(itemView);
songNameTextView = itemView.findViewById(R.id.songTitleTextView);
songArtistTextView = itemView.findViewById(R.id.songArtistTextView);
durationTextView = itemView.findViewById(R.id.durationTextView);
moreOptionsImageView = itemView.findViewById(R.id.moreOptionsImageView);
}
}
@NonNull
@Override
public PlayListView onCreateViewHolder(ViewGroup parent, int viewType) {
View layoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.playlist_detail_layout, parent, false);
return new PlayListView(layoutView);
}
@Override
public void onBindViewHolder(final PlayListView holder, final int position) {
HwAudioPlayItem currentItem = myPlayList.get(holder.getAdapterPosition());
holder.songNameTextView.setText(currentItem.getAudioTitle());
holder.songArtistTextView.setText(currentItem.getSinger());
long durationOfSong = currentItem.getDuration();
String totalDurationText = String.format(Locale.US, "d:d",
TimeUnit.MILLISECONDS.toMinutes(durationOfSong),
TimeUnit.MILLISECONDS.toSeconds(durationOfSong) -
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(durationOfSong))
);
holder.durationTextView.setText(totalDurationText);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(onItemClickListener != null) {
onItemClickListener.onItemClick(myPlayList, position);
}
}
});
}
@Override
public int getItemCount() {
return myPlayList.size();
}
}
This is one of the efficient and expandable (or let’s say: future-proof) ways of implementing onClickListener for a RecyclerView. There are other methods too and if you are knowledgable, you can implement your own way clicking RecyclerView items.
Since in part 1, I assumed some level of Android knowledge for this tutorial, I will not explain everything in the adapter, because it is not very very different than a normal custom adapter. I additionally implemented button interfaces. My constructor only has the playlist element, which I later process to view it in the playlist. I still convert my time 00:00 format because it is still a long value.
Also, do not forget to do this in your MainActivity:
And, because of that, you must implement onItemClick(…) method in the activity.
Code:
@Override
public void onItemClick(List myPlayList, int position) {
if (mHwAudioPlayerManager != null && mHwAudioQueueManager != null && mHwAudioQueueManager.getAllPlaylist() != null) {
/*
* 1. Obtains a playlist using the mHwAudioQueueManager.getAllPlaylist() method.
* 2. Compare myPlayList with mHwAudioQueueManager.
*/
if (mHwAudioQueueManager.getAllPlaylist() == myPlayList) {
//If the two playlists are the same, the user-specified song is played.
mHwAudioPlayerManager.play(position);
} else {
//If the two playlists are different, the mHwAudioPlayerManager playlist is updated.
//The music specified by the user is played.
mHwAudioPlayerManager.playList(playList, position, 0);
mHwAudioPlayerManager.setPlayMode(0);
mHwAudioQueueManager.setPlaylist(playList);
Log.w("Playlist", mHwAudioQueueManager.getAllPlaylist() + "");
}
}
}
/*
@Override
public void onItemClick(int position) {
if(mHwAudioPlayerManager != null){
mHwAudioPlayerManager.play(position);
}
}*/
And in MainActivity’s onItemClick(…) method, comments are put to further explain the code. If you do not like the verbose and think that this code looks complex, just comment the whole method and uncomment the below method (which is the same method with a simpler implementation). Be aware though, you should test it yourself to see whether it works for all cases.
Should you have any other questions regarding here (or anywhere else), please comment below, so I can address them.
Control Visibility
Now that our adapter is ready, we should control when/how the user can open it and when/how s/he can close it.
As I said before, my method may be a bit unconventional, so if you think you have a better idea you can implement it yourself. However, what I do is to add a constraint layout to the screen from the cover image to the bottom of the screen. Then, I control its visibility from GONE to VISIBLE, whenever the user clicks on the music button; and from VISIBLE to GONE whenever the user clicks on the music button and additionally, clicks on the back button.
Programmatically, I control the visibility in onCreate(…) method of the activity. And for the back button I override the onBackPressed(…) method. You can comment the onBackPressed(…) method completely and run the app, to see why I did it. This completely for user experience, in case the user wants to close the playlist with the back button click. I do it like this, it is simple enough to code them both:
Code:
@Override
protected void onCreate(Bundle savedInstanceState) {
//... your other codes
binding.containerLayout.setVisibility(View.GONE);
binding.playlistImageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(binding.containerLayout.getVisibility() == View.GONE){
binding.containerLayout.setVisibility(View.VISIBLE);
}
else{
binding.containerLayout.setVisibility(View.GONE);
}
}
});
}
@Override
public void onBackPressed() {
if(binding.containerLayout.getVisibility() == View.VISIBLE){
binding.containerLayout.setVisibility(View.GONE);
}
else{
super.onBackPressed();
}
}
At first, I programmatically ensuring that the visibility of constraint layout is gone. You can also make it gone in the layout screen after you are done with the xml changes.
You should be done with the playlist now.
Advanced Playback Controls
Although they are called advanced controls, with the help of Huawei AudioKit, they are simpler to implement than they sound.
AudioKit offers 4 playback modes:
Code:
Playback modes:
0: sequential playback
1: shuffling songs
2: repeating a playlist
3: repeating a song
If you remember the code above, I set the playback mode as 0 in onItemClick(…) method, because we want the playback to be sequential if the user does not change anything explicitly.
Code:
protected void onCreate(Bundle savedInstanceState) {
//... your other codes
final Drawable shuffleDrawable = getDrawable(R.drawable.menu_shuffle_normal);
final Drawable orderDrawable = getDrawable(R.drawable.menu_order_normal);
final Drawable loopItself = getDrawable(R.drawable.menu_loop_one_normal);
final Drawable loopPlaylist = getDrawable(R.drawable.menu_loop_normal);
binding.shuffleButtonImageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(mHwAudioPlayerManager != null){
if(binding.shuffleButtonImageView.getDrawable().getConstantState().equals(shuffleDrawable.getConstantState())){
mHwAudioPlayerManager.setPlayMode(0);
binding.shuffleButtonImageView.setImageDrawable(getDrawable(R.drawable.menu_order_normal));
Toast.makeText(MainActivity.this,"Normal order",Toast.LENGTH_SHORT).show();
}
else if(binding.shuffleButtonImageView.getDrawable().getConstantState().equals(orderDrawable.getConstantState())){
mHwAudioPlayerManager.setPlayMode(1);
binding.shuffleButtonImageView.setImageDrawable(getDrawable(R.drawable.menu_shuffle_normal));
Toast.makeText(MainActivity.this,"Shuffle songs",Toast.LENGTH_SHORT).show();
}
}
}
});
binding.loopButtonImageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(mHwAudioPlayerManager != null){
if(binding.loopButtonImageView.getDrawable().getConstantState().equals(loopItself.getConstantState())){
mHwAudioPlayerManager.setPlayMode(2);
binding.loopButtonImageView.setImageDrawable(getDrawable(R.drawable.menu_loop_normal));
Toast.makeText(MainActivity.this,"Loop playlist",Toast.LENGTH_SHORT).show();
}
else if(binding.loopButtonImageView.getDrawable().getConstantState().equals(loopPlaylist.getConstantState())){
mHwAudioPlayerManager.setPlayMode(3);
binding.loopButtonImageView.setImageDrawable(getDrawable(R.drawable.menu_loop_one_normal));
Toast.makeText(MainActivity.this,"Loop the song",Toast.LENGTH_SHORT).show();
}
}
}
});
}
Let’s understand here. I get my drawables at first to compare them to each other. (You should know where to get them from by now, if you do not have them already.) After that, I implement onClicks of the buttons and change the playback modes as per the list I have given above. Also, I change the drawable to current playback drawable for better usability. At the end of every change, I notify the user about the change so that s/he knows what s/he just has changed into.
This feature also gives a good competitive edge, because with these buttons implemented, our music player looks more professional.
That is the end of my tutorial. I hope that you have benefitted from it. If you have any questions about any part of this tutorial, please comment in the related section. See you in the next tutorial!
Hello everyone, in this article we will learn how to use Huawei Awareness Kit with foreground service to send notification when certain condition is met.
{
"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 Awareness Kit enables us to observe some environmental factors such as time, location, behavior, audio device status, ambient light, weather and nearby beacons. So, why don’t we create our own conditions to be met and observe them even when the application is not running?
First of all, we need to do HMS Core integration to be able to use Awareness Kit. I will not go into the details of that because it is already covered here.
If you are done with the integration, let’s start coding.
Activity Class
We will keep our activity class pretty simple to prevent any confusion. It will only be responsible for starting the service:
Java:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent serviceStartIntent = new Intent(this, MyService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceStartIntent);
}
else {
startService(serviceStartIntent);
}
}
}
However, we need to add the following permission to start a service correctly:
XML:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Service Class
Now, let’s talk about the service class. We are about to create a service which will run even when the application is killed. However, this comes with some restrictions. Since the Android Oreo, if an application wants to start a foreground service, it must inform the user by a notification which needs to be visible during the lifetime of the foreground service. Also, this notification needs to be used to start foreground. Therefore, our first job in the service class is to create this notification and call the startForeground() method with it:
Java:
Notification notification;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
notification = createCustomNotification();
else
notification = new Notification();
startForeground(1234, notification);
And here is how we create the information notification we need for SDK versions later than 25:
Java:
@RequiresApi(api = Build.VERSION_CODES.O)
private Notification createCustomNotification() {
NotificationChannel notificationChannel = new NotificationChannel("1234", "name", NotificationManager.IMPORTANCE_HIGH);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
manager.createNotificationChannel(notificationChannel);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, "com.awarenesskit.demo");
return notificationBuilder
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("Observing headset status")
.setPriority(NotificationManager.IMPORTANCE_HIGH)
.build();
}
Note: You should replace the application id above with the application id of your application.
Now, it is time to prepare the parameters to create a condition to be met called barrier:
Java:
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
headsetBarrierReceiver = new HeadsetBarrierReceiver();
registerReceiver(headsetBarrierReceiver, new IntentFilter(barrierReceiverAction));
AwarenessBarrier headsetBarrier = HeadsetBarrier.connecting();
createBarrier(this, HEADSET_BARRIER_LABEL, headsetBarrier, pendingIntent);
Here we have sent the required parameters to a method which will create a barrier for observing headset status. If you want, you can use other awareness barriers too.
Creating a barrier is a simple and standard process which will be taken care of the following method:
Java:
private void createBarrier(Context context, String barrierLabel, AwarenessBarrier barrier, PendingIntent pendingIntent) {
BarrierUpdateRequest.Builder builder = new BarrierUpdateRequest.Builder();
BarrierUpdateRequest request = builder.addBarrier(barrierLabel, barrier, pendingIntent).build();
Awareness.getBarrierClient(context).updateBarriers(request)
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void void1) {
System.out.println("Barrier Create Success");
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
System.out.println("Barrier Create Fail");
}
});
}
We will be done with the service class after adding the following methods:
Java:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
@Override
public void onDestroy() {
unregisterReceiver(headsetBarrierReceiver);
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
And of course, we shouldn’t forget to add our service to the manifest file:
XML:
<service
android:name=".MyService"
android:enabled="true"
android:exported="true" />
Broadcast Receiver Class
Lastly, we need to create a broadcast receiver where we will observe and handle the changes in the headset status:
Java:
public class HeadsetBarrierReceiver extends BroadcastReceiver {
public static final String HEADSET_BARRIER_LABEL = "HEADSET_BARRIER_LABEL";
@Override
public void onReceive(Context context, Intent intent) {
BarrierStatus barrierStatus = BarrierStatus.extract(intent);
String barrierLabel = barrierStatus.getBarrierLabel();
int barrierPresentStatus = barrierStatus.getPresentStatus();
if (HEADSET_BARRIER_LABEL.equals(barrierLabel)) {
if (barrierPresentStatus == BarrierStatus.TRUE) {
System.out.println("The headset is connected.");
createNotification(context);
}
else if (barrierPresentStatus == BarrierStatus.FALSE) {
System.out.println("The headset is disconnected.");
}
}
}
When a change occurs in the headset status, this method will receive the information. Here, the value of barrierPresentStatus will determine if headset is connected or disconnected.
At this point, we can detect that headset is just connected, so it is time to send a notification. The following method will take care of that:
Java:
private void createNotification(Context context) {
// Create PendingIntent to make user open the application when clicking on the notification
PendingIntent pendingIntent = PendingIntent.getActivity(context, 1234, new Intent(context, MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, "channelId")
.setSmallIcon(R.drawable.ic_headset)
.setContentTitle("Cool Headset!")
.setContentText("Want to listen to some music ?")
.setContentIntent(pendingIntent);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel mChannel = new NotificationChannel("channelId", "ChannelName", NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(mChannel);
}
notificationManager.notify(1234, notificationBuilder.build());
}
Output
When headset is connected, the following notification will be created even if the application is not running:
Final Thoughts
We have learned how to use Barrier API of Huawei Awareness Kit with a foreground service to observe the changes in environmental factors even when the application is not running.
As you may notice, the permanent notification indicating that the application is running in the background is not dismissible by the user which can be annoying. Even though these notifications can be dismissed by some third party applications, not every user has those applications, so you should be careful when deciding to build such services.
In this case, observing the headset status was just an example, so it might not be the best scenario for this use case, but Huawei Awareness Kit has many other great features that you can use with foreground services in your projects.
References
You can check the complete project on GitHub.
Note that, you will not be able to run this project because you don’t have the agconnect-services.json file for it. Therefore, you should only take it as a reference to create your own project.
Does Awareness Kit support wearable devices such as smart watches?