==================== Working With Sensors ==================== 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. .. code-block:: kotlin 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: .. code-block:: kotlin 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 with ``SENSOR_DELAY_NORMAL``, which is the default. You could also use ``SENSOR_DELAY_FASTEST`` to get sensor data as fast as possible, or ``SENSOR_DELAY_GAME`` to get sensor data at a rate suitable for games. .. code-block:: kotlin 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). .. code-block:: kotlin 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. 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. 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: .. code-block:: groovy implementation 'com.google.android.gms:play-services-location:17.0.0' #. Then request this permission with the uses-permission element in your app manifest: .. code-block:: xml #. Now, create an instance of ``FusedLocationProviderClient`` and call the ``getLastLocation`` method on this object to request the last known location: .. code-block:: kotlin private lateinit var fusedLocationClient: FusedLocationProviderClient override fun onCreate(savedInstanceState: Bundle?) { fusedLocationClient = LocationServices.getFusedLocationProviderClient(this) fusedLocationClient.lastLocation.addOnSuccessListener { location : Location? -> } } #. Create a ``button`` and a ``textView`` in ``activity_main.xml``, so that when the button is clicked, the app starts to get ``latitude`` and ``longitude`` of the location, which will be displayed in ``textView``. #. Using location services requires special run-time permission. The following code requests run time-permission for ``ACCESS_COARSE_LOCATION``: .. code-block:: kotlin // 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 the ``setOnClickListener``, 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 in ``textView``: .. code-block:: kotlin 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.