14. Working With Sensors¶
14.1. Motion Sensor¶
Motion sensors are useful for monitoring device movement, such as tilt, shake, rotation, or swing. The movement is usually a reflection of direct user input, but it can also be a reflection of the physical environment in which the device is sitting.
All of the motion sensors return multi-dimensional arrays of sensor values for each SensorEvent
. For example, the accelerometer returns acceleration force data for the three coordinate axes.
In this exercise, we will use accelerometer and display sensor values. These values can be used in many ways - e.g., in mobile games where the user moves an object by tilting the device.
-
Create a new empty project, and remove the Hello World TextView.
-
Implement the
SensorEventListener
interface, including all methods.class MainActivity : AppCompatActivity(),SensorEventListener{ ... override fun onAccuracyChanged(event: Sensor?, p1: Int) { } override fun onSensorChanged(event: SensorEvent) { } }
-
The following code shows you how to get an instance of the default acceleration sensor:
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
-
Then register a
SensorEventListener
for the given sensor at the desired sampling frequency. In this case, it is registered withSENSOR_DELAY_NORMAL
, which is the default. You could also useSENSOR_DELAY_FASTEST
to get sensor data as fast as possible, orSENSOR_DELAY_GAME
to get sensor data at a rate suitable for games.sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL)
-
Now, get sensor data and display in a
TextView
.event.values[0]
is the acceleration force along the x axis (including gravity).event.values[1]
is the acceleration force along the y axis (including gravity).event.values[2]
is the acceleration force along the z axis (including gravity).override fun onSensorChanged(event: SensorEvent) { textView.setText("x = ${event.values[0]}\n" +"y = ${event.values[1]}\n" +"z = ${event.values[2]}\n") }
Note: To measure the real acceleration of the device, the force of gravity must be removed from the accelerometer data.
14.2. User Location¶
There are several ways that the location of a device can be determined: GPS, Wi-Fi, and cell signal. However, getting location requires a lot of power, so we need to take battery usage into account. In general, high accuracy ,high frequency and low latency lead to more battery usage.
Here are several ways to define accuracy:
-
PRIORITY_HIGH_ACCURACY
provides the most accurate location possible, computed using as many inputs as necessary (GPS, Wi-Fi, cell, and a variety of Sensors), and may cause significant battery drain. -
PRIORITY_BALANCED_POWER_ACCURACY
provides accurate location while optimising for power. Very rarely uses GPS. Typically uses a combination of Wi-Fi and cell information to compute device location. -
PRIORITY_LOW_POWER
largely relies on cell towers and avoids GPS and Wi-Fi inputs, providing city-level accuracy with minimal battery drain. -
PRIORITY_NO_POWER
receives locations passively from other apps for which location has already been computed.
The PRIORITY_BALANCED_POWER_ACCURACY
and PRIORITY_LOW_POWER
options satisfy most apps’ needs.
Here are several ways to define frequency:
-
setinterval()
specifies the interval at which location is computed for your app. -
setFastestInterval()
to specify the interval at which location computed for other apps is delivered to your app.
Latency can be set using the setMaxWaitTime()
method, typically passing a value that is several times larger than the interval specified in the setInterval()
method. This setting delays location delivery, and multiple location updates may be delivered in batches. These two changes help minimise battery consumption.
14.2.1. Get the last known location¶
getLastLocation()
returns the most recently available location (which in rare cases may be null). This method provides a simple way of getting location and doesn’t incur costs associated with actively requesting location updates.
In most cases, you are interested in the user’s current location, which is usually equivalent to the last known location of the device.
The fused location provider is one of the location APIs in Google Play services. It manages the underlying location technology and provides a simple API so that you can specify requirements at a high level, like high accuracy or low power. It also optimises the device’s use of battery power.
-
To use
fused location provider
, first we need to set up Google Play services. In app.gradle, add:implementation 'com.google.android.gms:play-services-location:17.0.0'
-
Then request this permission with the uses-permission element in your app manifest:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
-
Now, create an instance of
FusedLocationProviderClient
and call thegetLastLocation
method on this object to request the last known location:private lateinit var fusedLocationClient: FusedLocationProviderClient override fun onCreate(savedInstanceState: Bundle?) { fusedLocationClient = LocationServices.getFusedLocationProviderClient(this) fusedLocationClient.lastLocation.addOnSuccessListener { location : Location? -> } }
-
Create a
button
and atextView
inactivity_main.xml
, so that when the button is clicked, the app starts to getlatitude
andlongitude
of the location, which will be displayed intextView
. -
Using location services requires special run-time permission. The following code requests run time-permission for
ACCESS_COARSE_LOCATION
:// If permission is not granted if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)!= PackageManager.PERMISSION_GRANTED) { // If this is not the first time it required permission, explain the purpose of this permission // After user's response, try again to ask permission if (ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.ACCESS_COARSE_LOCATION)) { val builder = AlertDialog.Builder(this) builder.setMessage("You have to give permission to access feature") .setPositiveButton("OK", DialogInterface.OnClickListener { dialog, i -> ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION), 1 ) }) .setNegativeButton("Cancel", DialogInterface.OnClickListener { dialog, i -> dialog.dismiss() }) .create() .show() } // If this is the first time asking permission, request permission without explanation else { ActivityCompat.requestPermissions( this, arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION), 1 ) } } else { // Permission is granted }
-
Now you can add a
setOnClickListener
on the button, and place all the code associated with requesting permission inside thesetOnClickListener
, so that the app only requires permission if the user wants to get their location. -
Then place the code to get the last location in the ‘Permission is granted’
else
block. The lambda function that is executed when last location is retrieved successfully should display the latitude and longitude intextView
:fusedLocationClient.lastLocation.addOnSuccessListener { location: Location? -> if (location != null) { var latitude = location.latitude var longitude = location.longitude textView.setText("Latitude = ${latitude}" + "\nLongitude = ${longitude}") } }
Note that
location
may be null in the following situations:-
Location is turned off in the device settings. The result could be null even if the last location was previously retrieved because disabling location also clears the cache.
-
The device never recorded its location, which could be the case of a new device or a device that has been restored to factory settings.
-
Google Play services on the device has restarted, and there is no active
FusedLocationProviderClient
that has requested location after the services restarted. To avoid this situation you can create a new client and request location updates yourself.
-