[MUSIC] In the example application that we just looked at, I tried to hold the device perfectly straight up. And if I had been able to do that, the Accelerometer would ideally have reported values around x equals zero meters per second squared, y equals 9.81 meters per second squared, or z equals zero meters per second squared. But as you saw in the example application, the accelerometer's values fluctuated. All applications will experience this kind of thing, due to natural user movements, non-flat surfaces, electrical noise, and so forth. When creating sensor enabled applications, developers will often apply transforms to the raw data to smooth it out. Two common kinds of transforms are called low pass filters and high pass filters. Let's talk about each one of those one at a time. Low-pass filters are used to be emphasized small, transient force changes while emphasizing the long term constant forces. You might use a low-pass filter when your application needs to pay attention to the constant force of gravity, for example, and you don't want to be affected just because your hand shakes a little. A real-life example of this would be something like a carpenter's level. The bubble needs to move based on gravity, not based on small hand twitches. In contrast, you use a high-pass filter when you want to emphasize the transient force changes. And you want to deemphasize the constant force components. You might use a high pass filter when you're application should ignore the constant force of gravity for example. But should respond to the specific moves that the user makes. A real life example of this might be a percussion instrument like a set of maracas. You don't really care about gravity here. You care about how the user is shaking the instrument. The next application is called SensorFilteredAccelerometer. This application applies both a low-pass and a high-pass filter to the raw accelerometer values. And then it displays the filtered values. Let's run it. Now I'll start up the sensor filtered accelerometer application. As you can see, this application displays nine text views with numbers in them. These numbers correspond to the x, y, and z values being read from the device's accelerometer. The raw values after applying a low pass filter and those raw values after applying a high pass filter. If we let the application run for a while, you'll see that the low pass values begin to approximate our ideal accelerometer readings. Roughly zero for the x and z axes. And roughly 9.81 for the y axis. At the same time you can see that the high pass values all tend toward zero. If I rotate the device counter-clockwise. You see the High Pass x value go positive. And if I rotate the device clockwise, you'll see the High Pass X value go negative. Let's look at the source code for this application. Here's the sensor filtered accelerometer application opened in the IDE. Now, I'll open the main activity. And notice again that this class implements the sensor event listener interface. So, it can receive callbacks from the sensor manager. Now in OnCreate, the application gets a reference to the sensor manager. Next, it gets a reference to the device's accelerometer by calling SensorManager.getDefaultSensor, passing in the type constant that corresponds to the accelerometer. In the onResume method, the application registers this class as a listener. For accelerometer events by calling the register listener method. And next the on pause method unregisters this class as a listener for any sensors. To which it may be listening. Scrolling down. We now come to the on sensor changed method. And as before, this method first checks to make sure that this event is an accelerometer reading. And then, it checks that a certain amount of time has passed since the last reading was displayed. If it has, the code records the accelerometers x, y, and z values. And then applies the low-pass filter to each of the raw values, after which the code applies the high-pass filter to each of the raw values. Let's look at the code for the two filters. Here's the low-pass method, which computes the low-pass filter. This method takes two parameters, a current reading and the long term average. It then computes the filtered value as a, as a kind of weighted average. In this case the filtered value equals 80% of the long term average plus 20% of the current reading. Over time, this calculation moves towards the ideal values that we talked about earlier. Scrolling down, here's the highPass method which computes the highPass filtered values. And this method also takes two parameters, the current reading and the long-term average, which is actually computed by the low pass method that we just talked about. This code then subtracts the long-term average from the current reading, and therefore represents the part of the reading that is not due to gravity. This example application is called SensorCompass. This application uses the device's accelerometer and its magnetometer to orient a compass arrow towards magnetic north. Now, I'll start up the SensorCompass application. As you can see, this application displays a green circle with a white arrow. Right now, this arrow points towards magnetic north. However, if I begin to rotate the device, you see that the arrow continues to point towards the north. Which, of course, is exactly what a compass should do. Let's look at the source code for this application. Here's the sensor compass application open in the IDE. Now, I'll open the main activity. Let's scroll down to the onCreate method. As with the other applications, this one begins by setting up the user interface. And, in particular, it creates a custom view that holds the compass arrow. And then it adds that view to the activities main view. It then gets a reference to the sensor manager. After that it gets a reference to the device's accelerometer. And it gets a reference to the device's magnetometer. By calling Sensor<anager.get default sensor. And by passing in the appropriate type constants. In the on resume method the code registers this class as a listener for accelerometer events and for magnetometer events by calling the register listener method. The on pause method unregisters its class as a listener for all sensors. The on sensor change method processes the incoming sensor events. This method first checks whether the event is an accelerometer or a magnetometer event. And then copies the appropriate event data. Next, if there are readings from each of the two sensors, the code calls the SensorManager.getRotationMatrix method, passing in the sensor readings and an array in which to store the rotation matrix. If that method was successful, then the code called the get SensorManager.getOrientation method. Passing in the rotation matrix that we just acquired from the call to get rotationMatrix. It also passes in another array called orientationMatrix. When this method returns, orientation matrix will hold the information the application needs to determine how the device is oriented with respect to the Earth's magnetic North. The code then grabs this value from the orientation matrix. And since this value is measured in radians the code then converts the radian value to degrees. After that, the code invalidates the compass arrow view, and then clears the arrays that hold the sensor readings. Let's look at the compass arrow view to see how it uses the new orientation information. Scrolling down to the onDraw method, the code first saves the current canvas and then it rotates this view on the canvas by an amount equal to minus one times m rotation in degrees. So basically the idea here is that if the device is pointing say 90 degrees away from north, then the compass arrow must rotate back 90 degrees in order for the compass arrow to keep pointing north.