Android Wearables (2015)
Part III. Projects
Chapter 9. Android Wear as Activity Tracker
WHAT’S IN THIS CHAPTER?
· ➤ Introduction to activity trackers
· ➤ Working with sensors on Wear
· ➤ Displaying data on small screens
WROX.COM CODE DOWNLOADS FOR THIS CHAPTER
The code downloads for this chapter are found at www.wrox.com/go/androidwearables on the Download Code tab. The code is in the Chapter 9 download and the files are individually named according to the listing numbers noted throughout the chapter.
WHAT ARE ACTIVITY TRACKERS?
Activity Trackers, as their name implies, are sensors that we wear on our bodies or in our hands to store data about our physical activities so that we can later review the statistics. A 2009 article published in the Harvard Health Letter stated that pedometers increase our physical activity by motivating us to achieve simple goals such as taking 10,000 steps every day.
There are several different kinds of Activity Trackers. Some have simple functionality, and others have more-complex combinations of sensors. Special apps for your smartphone can do the same things as a dedicated Activity Tracker. The past few years these devices and apps have risen steadily in popularity among regular people. Professional athletes have used them for quite some time. For example, an Activity Tracker can be used to collect a football player’s statistics during games with the goal of maximizing his output on the field.
A number of devices are available for people who enjoy sports as a hobby, not a profession. These include the Nike+ FuelBand, the Sony SmartBand, and the Gear Fit from Samsung. Another popular brand is Fitbit, which has, to this author’s knowledge, three different devices aimed at different types of practitioners. Of course, these are just a few of the available ones. Describing all their differences is beyond the scope of this chapter. We’ll explore how to create your own basic Activity Tracker application for Android Wear.
WEAR AS AN ACTIVITY TRACKER
The typical Android Wear device is basically the same as an Activity Tracker, only packaged in a slightly different way. Android Wear devices already have the core sensors required to measure activity. And because the device usually is connected to your phone, it can access all the phone’s resources and sensors as well, making it a powerful—although bloated—competitor to the dedicated devices.
In fact, Android Wear devices come packaged with a simple Activity Tracker app called Fit, as shown in Figure 9.1. It doesn’t offer anything near the complexity of a real Activity Tracker device, but it’s a decent starting point if you want to become more physically active without having to sign up for a gym membership.
FIGURE 9.1 The Fit app
The Fit app uses the new sensors introduced with Android Kit Kat (API 19) called Step Detector and Step Counter. They’re a new breed of power-efficient composite sensors that use a special hardware sensor based on an accelerometer to detect when the user has taken a step.
The new sensor is good at determining when a step has been taken. It works for walking, running, and climbing stairs. It also attempts to ignore when you’re driving or when you’re riding a bike or train.
Step Detector
The Step Detector, as its name implies, triggers when the person wearing the smartwatch takes a step. This sensor can deliver results to the user fairly quickly. On the other hand, it has a rather high rate of false positives, meaning it can trigger a step detection even when the user hasn’t taken a step.
This rate of false positives can be minimized by applying another new feature of Kit Kat—batching sensor values. Batching does not remove any sensor readings, but it delivers them with some delay. Doing so conserves power and may improve readings.
The Step Detector sensor doesn’t require any special permissions, but because it’s a sensor that requires special hardware, it’s highly recommended that you include a uses-feature in your manifest if you plan on working with it. See Listing 9-1.
LISTING 9-1: Adding the uses-feature element for the Step Detector
<uses-feature android:name="android.hardware.sensor.stepdetector" />
Step Counter
Much like the Step Detector, the Step Counter uses the new hardware sensor introduced in Kit Kat. The big difference between the counter and the detector is how they work. The Step Detector delivers uncertain values at a high pace, and the Step Counter delivers more certain values at a low pace.
The value Step Counter delivers is the accumulated number of steps taken since the device booted up. So if your device has been “alive” for a long time without rebooting, this value may be very high.
Just like the Step Detector, this sensor requires special hardware. Therefore, you should add the uses-feature shown in Listing 9-2.
LISTING 9-2: The uses-feature element for Step Counter
<uses-feature android:name="android.hardware.sensor.stepcounter" />
These new sensors were introduced in Kit Kat (API 19), but because you’re using them on a Wear device (API 20, or 4.4W), you don’t need to change the minimum required SDK.
You’re now ready to begin building your own Activity Tracker using Android Wear. You’ll make a simple app called WalkKeeper for people who enjoy walking for exercise.
NOTE
Technically, the hardware required for the steps sensors aren’t new per se. It’s an extra, very power-efficient processor dedicated to reading and interpreting the data coming in from the activity sensors, such as the accelerometer. It’s very similar to the Apple co-processors (M7) motion processor which was introduced with iPhone 5S.
BUILDING THE WALKKEEPER APP
The WalkKeeper app, while active, tracks how many calories are burned by walking, and it does so live. First you must establish a basic algorithm for calculating how many calories are burned when walking. This can easily become a fairly complex equation, but we’ll keep it simple in this example and let you think about developing something more complex on your own.
Calculating Calories
You should consider several variables when calculating calories consumed by physical activities. Height, weight, gender, and number of steps taken are all integral parts of the equation, as shown in Table 9.1.
Table 9.1 Variables for Calculating Calories
VARIABLE |
LETTER(S) |
DESCRIPTION |
Stride Factor |
SF |
This is important because you’ll work a lot with averages in your app to simplify the interface. Assume that the average woman has an average walking stride of 0.413 times her height and that the average male has an average stride of 0.415 times his height. |
Height |
H |
This value, in centimeters, is needed to calculate the average stride length. |
Steps |
S |
This is the core of the app. The number of steps is needed to calculate how far the user has traveled. |
Weight |
W |
This value, in kilograms, is needed to calculate how many calories are burned with each mile the user walks. |
Given these variables, the equations for calculating the Stride Length (SL), in centimeters, look like this:
To get the total distance (D) travelled you use the stride length (SL) and the number of steps (S) taken by the user. Because the stride length is in centimeters you should also convert it to kilometers:
Here’s the equation for calculating the calories burned per distance (CBD) in kilometers:
NOTE
I use a factor of 2.02 because many health websites use this value to calculate calories burned when someone walks at an average speed. This is, of course, a very simplistic formula, which in reality consists of many more variables such as how fast you’re walking, how steep the path is, wind direction and strength, and more.
Here’s the equation for calculating the total number of calories burned (TCB) based on the number of steps the user takes:
If you apply the equations for calories burned per distance (CBD) and distance (D) you’ll get the following equation:
Finally, apply the equation for calculation of stride length (SL) and you’ll end up with a simplified equation for calculating the calories burned based on your weight, height, and the number of steps you’ve taken:
Creating the Project
Begin by creating a new project:
1. Call it WalkKeeper and enter the company domain, wrox.wiley.com.
2. Check both the Wear and the Phone and Tablet check boxes.
3. Choose Blank Activity to create a blank activity for mobile, and call it WalkKeeperActivity. We won’t actually use this activity in the example, but it’s good to have it for future developments.
4. Choose Blank Activity to create a blank activity for Wear, and call it SelectGenderActivity. This will be the starting activity on the Wear device.
5. Click Finish, and open your newly created SelectGenderActivity class.
Selecting Gender
The first activity we require in this application is a way to select a gender. We’ll use WearableListView for this purpose. Open the two layouts for your SelectGenderActivity activity—rect_activity_select_gender.xml and round_activity_select_gender.xml—and add theWearableListView element. Listing 9-3 shows the layout for the rectangular device. The round layout is pretty much identical; we’re not showing it here.
LISTING 9-3: Building the user interface for selecting a gender
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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"
android:orientation="vertical"
tools:context=".SelectGenderActivity"
tools:deviceIds="wear_square">
<android.support.wearable.view.WearableListView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
Switch to SelectGenderActivity and load WearableListView from the layout. We’re using the standard Activity class, so remember to use WatchViewStub.OnLayoutInflatedListener to attach the correct UI widgets. Listing 9-4 shows you how.
LISTING 9-4: Beginning SelectGenderActivity
package com.wiley.wrox.walkkeeper;
import android.app.Activity;
import android.os.Bundle;
import android.support.wearable.view.WatchViewStub;
import android.support.wearable.view.WearableListView;
public class SelectGenderActivity extends Activity {
private WearableListView mListView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_select_gender);
final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
@Override
public void onLayoutInflated(WatchViewStub stub) {
mListView = (WearableListView) stub.findViewById(R.id.list);
}
});
}
}
Next we create the adapter class. In Wear, adapters are simple. Everything is included, including the view holder pattern. So we just need to fill the adapter with the data, which is basic strings in this case, as shown in Listing 9-5.
LISTING 9-5: Creating StringListAdapter
package com.wiley.wrox.walkkeeper;
import android.content.Context;
import android.support.wearable.view.WearableListView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.TextView;
public class StringListAdapter extends WearableListView.Adapter {
private String[] data;
private LayoutInflater mLayoutInflater;
public StringListAdapter(Context context, String[] data) {
mLayoutInflater = LayoutInflater.from(context);
this.data = data;
}
@Override
public WearableListView.ViewHolder onCreateViewHolder(ViewGroup group, int i) {
return new WearableListView.ViewHolder(mLayoutInflater.inflate(R.layout
.stringlist_item, group, false));
}
@Override
public void onBindViewHolder(WearableListView.ViewHolder viewHolder, int i) {
TextView text = (TextView) viewHolder.itemView.findViewById(R.id
.stringlist_item_text);
text.setText(data[i]);
}
@Override
public int getItemCount() {
return data.length;
}
}
You’ll notice an error. We haven’t created the layout for the list item row. Listing 9-6 shows the simple layout we’ll use for all the WearableListView rows in this project.
LISTING 9-6: Building the list view row layout
<?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">
<TextView
android:id="@+id/stringlist_item_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="..."/>
</LinearLayout>
Instantiate StringListAdapter and connect it to WearableListView in SelectGenderActivity. In Listing 9-7, the changes in SelectGenderActivity are highlighted.
LISTING 9-7: Wrapping up SelectGenderActivity
package com.wiley.wrox.walkkeeper;
import android.app.Activity;
import android.os.Bundle;
import android.support.wearable.view.WatchViewStub;
import android.support.wearable.view.WearableListView;
public class SelectGenderActivity extends Activity {
private String[] mData = new String[]{"\u2640 female", "\u2642 male"};
private WearableListView mListView;
private StringListAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_select_gender);
final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
@Override
public void onLayoutInflated(WatchViewStub stub) {
mListView = (WearableListView) stub.findViewById(R.id.list);
mAdapter = new StringListAdapter(SelectGenderActivity.this, mdata);
mListView.setAdapter(mAdapter);
}
});
}
}
You should end up with a simple list that has two items, similar to Figure 9.2.
FIGURE 9.2 The select gender list
Finally, attach the listener to WearableListView, and save the selected value, as shown in Listing 9-8.
LISTING 9-8: Saving the selected gender and moving to the next activity
package com.wiley.wrox.walkkeeper;
import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.wearable.view.WatchViewStub;
import android.support.wearable.view.WearableListView;
public class SelectGenderActivity extends Activity {
private String[] mData = new String[]{"\u2640 female", "\u2642 male"};
private WearableListView mListView;
private StringListAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_select_gender);
final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
@Override
public void onLayoutInflated(WatchViewStub stub) {
mListView = (WearableListView) stub.findViewById(R.id.list);
mAdapter = new StringListAdapter(SelectGenderActivity.this, mdata);
mListView.setAdapter(mAdapter);
mListView.setClickListener(new WearableListView.ClickListener() {
@Override
public void onClick(WearableListView.ViewHolder viewHolder) {
String label = mdata[viewHolder.getPosition()];
if (label.contains("female")) {
saveGender("female");
} else {
saveGender("male");
}
openSelectWeightActivity();
}
@Override
public void onTopEmptyRegionClick() {
}
});
}
});
}
private void saveGender(String gender) {
SharedPreferences prefs = getPreferences(MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString("gender", gender);
editor.apply();
}
private void openSelectWeightActivity() {
// TODO: Start activity SelectWeightActivity
}
}
You’ll soon create the activity for selecting a specific weight. Before you create it you can create the intent for starting that activity, as shown in Listing 9-9.
LISTING 9-9: Open the SelectWeight activity
private void openSelectWeightActivity() {
Intent selectWeight = new Intent(this, SelectWeightActivity.class);
startActivity(selectWeight);
finish();
}
Selecting Weight
Create a new activity by selecting File ➢ New ➢ Activity ➢ Blank Wear Activity.
Call your new activity SelectWeightActivity, and uncheck the Launcher Activity check box before you click Finish.
In this activity we’ll use the same setup as the previous activity, with WearableListView attached to ClickListener. We’ll reuse the previously created StringListAdapter and layouts. Listing 9-10 shows SelectWeightActivity in its entirety. You’ll notice we decided to go with kilograms. You can, of course, change this. Just remember to modify how you calculate the calories later.
LISTING 9-10: SelectWeightActivity
package com.wiley.wrox.walkkeeper;
import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.wearable.view.WatchViewStub;
import android.support.wearable.view.WearableListView;
public class SelectWeightActivity extends Activity {
private String[] mData = new String[]{"60", "65", "70", "75", "80", "85", "90"};
private WearableListView mListView;
private StringListAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_select_weight);
final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
@Override
public void onLayoutInflated(WatchViewStub stub) {
mListView = (WearableListView) stub.findViewById(R.id.list);
mAdapter = new StringListAdapter(SelectWeightActivity.this, mdata);
mListView.setAdapter(mAdapter);
mListView.setClickListener(new WearableListView.ClickListener() {
@Override
public void onClick(WearableListView.ViewHolder viewHolder) {
String label = mdata[viewHolder.getPosition()];
saveWeight(Integer.parseInt(label));
openSelectHeightActivity();
}
@Override
public void onTopEmptyRegionClick() {
}
});
}
});
}
private void saveWeight(int weight) {
SharedPreferences prefs = getPreferences(MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putInt("W", weight);
editor.apply();
}
private void openSelectHeightActivity() {
// TODO: Start activity SelectHeightActivity
}
}
Add WearableListView to the layouts, and you’re done with this activity. Listing 9-11 shows the rectangular layout version; the round layout is nearly identical.
LISTING 9-11: Adding WearableListView to the Select Weight layouts
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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"
android:orientation="vertical"
tools:context="com.wiley.wrox.walkkeeper.SelectWeightActivity"
tools:deviceIds="wear_square">
<android.support.wearable.view.WearableListView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
The finished Select Weight view should look something like Figure 9.3.
FIGURE 9.3 The finished select weight list
To open the SelectHeight activity, which is still not created, add the intent inside openSelectHeightActivity method and call startActivity, as shown in Listing 9-12. You’ll get an error but that will be quickly remedied in the following section.
LISTING 9-12: Start the SelectHeight activity
private void openSelectHeightActivity() {
Intent selectHeight = new Intent(this, SelectHeightActivity.class);
startActivity(selectHeight);
finish();
}
Selecting Height
The last setup step for the WalkKeeper app is the Select Height activity. Again, this is a list with a set number of available heights.
1. Create a new activity by selecting File ➢ New ➢ Activity ➢ Blank Wear Activity.
2. Name the new activity SelectHeightActivity, and unselect the Launcher Activity check box.
3. Open your new activity, and add the highlighted code shown in Listing 9-13.
LISTING 9-13: The Select Height activity
package com.wiley.wrox.walkkeeper;
import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.wearable.view.WatchViewStub;
import android.support.wearable.view.WearableListView;
public class SelectHeightActivity extends Activity {
private String[] mData = new String[]{"160", "165", "170", "175", "180"};
private WearableListView mListView;
private StringListAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_select_height);
final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
@Override
public void onLayoutInflated(WatchViewStub stub) {
mListView = (WearableListView) stub.findViewById(R.id.list);
mAdapter = new StringListAdapter(SelectHeightActivity.this, mdata);
mListView.setAdapter(mAdapter);
mListView.setClickListener(new WearableListView.ClickListener() {
@Override
public void onClick(WearableListView.ViewHolder viewHolder) {
String label = mdata[viewHolder.getPosition()];
saveHeight(Integer.parseInt(label));
openWalkKeeperActivity();
}
@Override
public void onTopEmptyRegionClick() {
}
});
}
});
}
private void saveHeight(int height) {
SharedPreferences prefs = getPreferences(MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putInt("H", height);
editor.apply();
}
private void openWalkKeeperActivity() {
// TODO: Open WalkKeeperActivity
}
}
Finally, edit the layouts for the Select Height activity as shown in Listing 9-14.
LISTING 9-14: The Select Height layouts
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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"
android:orientation="vertical"
tools:context="com.wiley.wrox.walkkeeper.SelectHeightActivity"
tools:deviceIds="wear_square">
<android.support.wearable.view.WearableListView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
The finished Select Height activity should look like Figure 9.4.
FIGURE 9.4 Select Height activity
When the user is done selecting all of the preferences, it’s time to start the WalkKeeper activity which will present the active view to the user. Listing 9-15 shows how to create this intent.
LISTING 9-15: Start the WalkKeeper activity
private void openWalkKeeperActivity() {
Intent walkKeeper = new Intent(this, WalkKeeperActivity.class);
startActivity(walkKeeper);
finish();
}
The WalkKeeper Activity
The final and most important piece of the puzzle is the WalkKeeper activity, where all logic for calculating steps taken and calories burned happens. This is also where we display the live data to the user.
1. Start by creating a new activity. Select File ➢ New ➢ Activity ➢ Blank Wear Activity.
2. Name your activity WalkKeeperActivity, and uncheck the Launcher Activity check box.
Building the User Interface
On the main WalkKeeper user interface, we’ll display both the total number of steps taken during this session and the calories burned during the stroll. Listing 9-16 shows the entire layout with all the TextViews we’ll use.
LISTING 9-16: The WalkKeeper user interface
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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"
android:orientation="vertical"
tools:context="com.wiley.wrox.walkkeeper.WalkKeeperActivity"
tools:deviceIds="wear_square">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/title_steps"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Steps taken:"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:id="@+id/text_steps"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textAppearance="?android:attr/textAppearanceMedium"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/title_calories"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Calories burned"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:id="@+id/text_calories"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textAppearance="?android:attr/textAppearanceMedium"/>
</LinearLayout>
</LinearLayout>
Connecting the User Interface
We’ve used standard activities so far in the app. However, for this activity I want to allow the user to touch to close the app, as shown in Figure 9.5. Because of this I will let the WalkKeeperActivity extend InsetActivity rather than Activity. This will also make us load views in a different way—instead of using a WatchViewStub we’ll rely on the onReadyForContent() life-cycle method.
FIGURE 9.5 The touch-to-close option
Open WalkKeeperActivity.java and extend InsetActivity, as shown in Listing 9-17.
LISTING 9-17: Extending InsetActivity
package com.wiley.wrox.walkkeeper;
import android.os.Bundle;
import android.support.wearable.activity.InsetActivity;
public class WalkKeeperActivity extends InsetActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void onReadyForContent() {
if (!isRound()) {
setContentView(R.layout.rect_activity_walk_keeper);
} else {
setContentView(R.layout.round_activity_walk_keeper);
}
}
}
Load the user interface widgets into variables to enable changing them live. See Listing 9-18 for hints.
LISTING 9-18: Loading the user interface
package com.wiley.wrox.walkkeeper;
import android.os.Bundle;
import android.support.wearable.activity.InsetActivity;
import android.widget.TextView;
public class WalkKeeperActivity extends InsetActivity {
private TextView stepsCount, caloriesCount;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void onReadyForContent() {
if (!isRound()) {
setContentView(R.layout.rect_activity_walk_keeper);
} else {
setContentView(R.layout.round_activity_walk_keeper);
}
stepsCount = (TextView) findViewById(R.id.text_steps);
caloriesCount = (TextView) findViewById(R.id.text_calories);
}
}
Getting the Stored Settings
When we’re done with the user interface, we can move on to loading the previous saved user data—gender, weight, and height. See Listing 9-19 for details.
LISTING 9-19: Loading the stored user data
package com.wiley.wrox.walkkeeper;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.wearable.activity.InsetActivity;
import android.widget.TextView;
public class WalkKeeperActivity extends InsetActivity {
private TextView stepsCount, caloriesCount;
private String gender;
private int W, H;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedPreferences prefs = getPreferences(MODE_PRIVATE);
gender = prefs.getString("gender", "female");
W = prefs.getInt("W", 80);
H = prefs.getInt("H", 180);
}
@Override
public void onReadyForContent() {
if (!isRound()) {
setContentView(R.layout.rect_activity_walk_keeper);
} else {
setContentView(R.layout.round_activity_walk_keeper);
}
stepsCount = (TextView) findViewById(R.id.text_steps);
caloriesCount = (TextView) findViewById(R.id.text_calories);
}
}
Reading the Sensor Data
This example uses the Step Detector sensor because we want many quick readings for testing. We could use the Step Counter sensor as well, but I’ll leave that for you to implement if you want a more accurate reading. Add the uses-feature element for the Step Detector sensor as shown in Listing 9-20.
LISTING 9-20: Adding uses-feature for the Step Detector sensor
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="com.wiley.wrox.walkkeeper"
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.type.watch"/>
<uses-feature android:name="android.hardware.sensor.stepdetector"/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@android:style/Theme.DeviceDefault">
<activity
android:name=".SelectGenderActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".SelectWeightActivity"
android:label="@string/title_activity_select_weight">
</activity>
<activity
android:name=".SelectHeightActivity"
android:label="@string/title_activity_select_height">
</activity>
<activity
android:name=".WalkKeeperActivity"
android:label="@string/title_activity_walk_keeper">
</activity>
</application>
</manifest>
Open WalkKeeperActivity and declare SensorManager and Sensor variables. Then load them and attach a listener, as shown in highlights in Listing 9-21.
LISTING 9-21: Attaching the sensor to WalkKeeperActivity
package com.wiley.wrox.walkkeeper;
import android.content.SharedPreferences;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.support.wearable.activity.InsetActivity;
import android.widget.TextView;
public class WalkKeeperActivity extends InsetActivity {
private TextView stepsCount, caloriesCount;
private String gender;
private int W, H;
private SensorManager mSensorManager;
private Sensor mSensor;
private int S;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedPreferences prefs = getPreferences(MODE_PRIVATE);
gender = prefs.getString("gender", "female");
W = prefs.getInt("W", 80);
H = prefs.getInt("H", 180);
mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR);
}
@Override
protected void onResume() {
super.onResume();
mSensorManager.registerListener(mSensorEventListener, mSensor, 1000);
}
@Override
protected void onPause() {
super.onPause();
mSensorManager.unregisterListener(mSensorEventListener);
}
@Override
public void onReadyForContent() {
if (!isRound()) {
setContentView(R.layout.rect_activity_walk_keeper);
} else {
setContentView(R.layout.round_activity_walk_keeper);
}
stepsCount = (TextView) findViewById(R.id.text_steps);
caloriesCount = (TextView) findViewById(R.id.text_calories);
}
private SensorEventListener mSensorEventListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent sensorEvent) {
S += (int) sensorEvent.values[0];
}
@Override
public void onAccuracyChanged(Sensor sensor, int i) {
}
};
}
Now we’re getting the sensor values, which means we’re almost at the finish line. We just need to calculate the calories and then update the user interface.
Calculating and Updating the User Interface
Start by calculating the calories, as shown in Listing 9-22. Note that this example uses the metric system. You can use the formula from equation 3 in the earlier section “Calculating Calories.”
LISTING 9-22: Calculating the calories from the number of steps
package com.wiley.wrox.walkkeeper;
import android.content.SharedPreferences;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.support.wearable.activity.InsetActivity;
import android.widget.TextView;
public class WalkKeeperActivity extends InsetActivity {
private static final double STRIDE_FACTOR_FEMALE = 0.413;
private static final double STRIDE_FACTOR_MALE = 0.415;
private TextView stepsCount, caloriesCount;
private String gender;
private int W, H;
private SensorManager mSensorManager;
private Sensor mSensor;
private int S;
private double TCB;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedPreferences prefs = getPreferences(MODE_PRIVATE);
gender = prefs.getString("gender", "male");
W = prefs.getInt("W", 80);
H = prefs.getInt("H", 180);
mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR);
}
@Override
protected void onResume() {
super.onResume();
mSensorManager.registerListener(mSensorEventListener, mSensor, 1000);
}
@Override
protected void onPause() {
super.onPause();
mSensorManager.unregisterListener(mSensorEventListener);
}
@Override
public void onReadyForContent() {
if (!isRound()) {
setContentView(R.layout.rect_activity_walk_keeper);
} else {
setContentView(R.layout.round_activity_walk_keeper);
}
stepsCount = (TextView) findViewById(R.id.text_steps);
caloriesCount = (TextView) findViewById(R.id.text_calories);
}
private SensorEventListener mSensorEventListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent sensorEvent) {
S += (int) sensorEvent.values[0];
TCB = getCalories();
updateUserInterface();
}
@Override
public void onAccuracyChanged(Sensor sensor, int i) {
}
};
private double getCalories() {
double CBD = W * 2.02;
double D = 0;
if (gender.equals("female")) {
D = H * STRIDE_FACTOR_FEMALE * S * 0.00001;
}else{
D = H * STRIDE_FACTOR_MALE * S * 0.00001;
}
return CBD * D;
}
private void updateUserInterface(){
if( stepsCount != null ){
stepsCount.setText(Integer.toString(S));
}
if( caloriesCount != null ){
caloriesCount.setText(Double.toString(TCB));
}
}
}
Keeping the Activity Open
As you know, Android Wear automatically closes activities after a short period. But we’ll keep the activity running until the user actively closes it by touching the screen. This lets us avoid having to store any state in the activity—something you can explore on your own.
Keeping the activity open in this way will drain significantly more battery than normal. A more correct way of solving this problem would be using a Service that collects data and does the calculations in the background. This way the Activity can be destroyed without losing any data.
There are multiple ways to keep the screen awake. We’ll apply a special flag during startup of the WalkKeeper activity, as shown in Listing 9-23.
LISTING 9-23: Keeping the screen awake
package com.wiley.wrox.walkkeeper;
import android.content.SharedPreferences;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.support.wearable.activity.InsetActivity;
import android.view.WindowManager;
import android.widget.TextView;
public class WalkKeeperActivity extends InsetActivity {
private static final double STRIDE_FACTOR_FEMALE = 0.413;
private static final double STRIDE_FACTOR_MALE = 0.415;
private TextView stepsCount, caloriesCount;
private String gender;
private int W, H;
private SensorManager mSensorManager;
private Sensor mSensor;
private int S;
private double TCB;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedPreferences prefs = getPreferences(MODE_PRIVATE);
gender = prefs.getString("gender", "male");
W = prefs.getInt("W", 80);
H = prefs.getInt("H", 180);
mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR);
}
@Override
protected void onResume() {
super.onResume();
mSensorManager.registerListener(mSensorEventListener, mSensor, 1000);
}
@Override
protected void onPause() {
super.onPause();
mSensorManager.unregisterListener(mSensorEventListener);
}
@Override
public void onReadyForContent() {
if (!isRound()) {
setContentView(R.layout.rect_activity_walk_keeper);
} else {
setContentView(R.layout.round_activity_walk_keeper);
}
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
stepsCount = (TextView) findViewById(R.id.text_steps);
caloriesCount = (TextView) findViewById(R.id.text_calories);
}
private SensorEventListener mSensorEventListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent sensorEvent) {
S += (int) sensorEvent.values[0];
TCB = getCalories();
upateUserInterface();
}
@Override
public void onAccuracyChanged(Sensor sensor, int i) {
}
};
private double getCalories() {
double CPD = W * 2.02;
double D = 0;
if (gender.equals("female")) {
D = H * STRIDE_FACTOR_FEMALE * S * 0.00001;
}else{
D = H * STRIDE_FACTOR_MALE * S * 0.00001;
}
return CPD * D;
}
private void upateUserInterface(){
if( stepsCount != null ){
stepsCount.setText(Integer.toString(S));
}
if( caloriesCount != null ){
caloriesCount.setText(Double.toString(TCB));
}
}
}
The finished WalkKeeper app should look something like Figure 9.6.
FIGURE 9.6 The finished WalkKeeper app
This is a simple demonstration of what Android Wear and the new hardware sensors available in Kit Kat can do. There are still a million things you can, and should, do with this app before it can be distributed on Google Play.
IMPROVEMENTS
Here are some things you should consider improving in this project that we haven’t discussed in this chapter:
· Move the data collection and calculations to a Service.
· Connect the app to Google Fit.
· Design a more attractive user interface that is specifically made for Wear. Remember the rule of thumb with Wear user interfaces: They should be glanceable.
· Another thing you could improve with this app are the transitions between activities.
· Another obvious improvement is asking the user for his or her weight only once, and then after that automatically calculating the most probable weight according to his or her activity history.
· Last, but not least, is improving the sensor readings. You could consider not only changing the sensors but including more sensors to expand the app’s functionality.
The final thing you should do with this app is create the companion mobile app. This task is not listed in the improvements because it’s not so much an improvement as it is a requirement to publish on Google Play. You need the companion app to distribute the app!
SUMMARY
In this chapter you’ve seen examples of use for some of the new user interface widgets introduced in Android Wear. You also were introduced to using passive sensors in Wear apps.
The next chapter deals with active use of sensors to create interesting interactions with your Wear device.