Device Location Awareness (DLA)
Overview
Device Location Awareness service allows Campaign Automation marketers to send location-based messages. To enable location-based messaging for marketers, you must configure the SDK for locations and location administrators use the DLA UI to define locations. For information, see Device Location Awareness.
Location events are generated and sent to the Campaign server where they are converted into Universal Behavior events. Marketers can then add location events to programs and queries.
Tip:
The app stores are increasingly concerned with battery life and privacy when locations are enabled in the apps. If your only justification of background location use is geo-targeted marketing messages, your app store submission may be rejected, or your approved app submission may later be pulled by Google/Apple. Therefore, you should ensure that you offer a valuable customer feature that utilizes background location. This approach allows you to utilize background location for other purposes, such as using our location support.
Configure the SDK for DLA
Device Location Awareness (DLA) is supported in the 3X Mobile App Messaging SDK for iOS.
- On the Project Settings screen for the target app, turn on the Background Modes capability in the Capabilities tab, and then enable Location updates and Background fetch.
- To use location services, add the NSLocation key and string to the Info.plist file for the app project.
NSLocationAlwaysUsageDescription requests permission from the mobile app user to use location services when your app is running in the background. Because Apple recently started prohibiting apps from sharing location data with third parties when explicit user consent is absent, you might want to use NSLocationAlwaysUsageDescription to tell users how the SDK uses location data. The SDK stores location data locally on the device and when an event is triggered, the event is sent to MCE, which a third party. The event does not contain raw location data. To convince users to grant permission, you might also want to let them know that battery usage is minimal when location sharing is enabled.
<key>NSLocationAlwaysUsageDescription</key> <string>The SDK stores location data locally on the device and when a geofence event is triggered, the event is sent to MCE, which a third party. The event does not contain raw location data. Battery usage is minimal when sharing location.</string>
- The app could request the "always" permission and the user would be prompted to allow for "while using" permission. Later, the app tries to use location in the background and fails for an unspecified amount of time. Until, at a certain point the user is asked if the app can have "always" location permission (typically while app is not running and within a day of original request).
- The app could instruct the user to manually open the settings app, select the app's settings and then grant the "always" location permission from there.
Note:
You might also consider using the NSLocationWhenInUseUsageDescription key to allow location access only when the app is in use.
iOS 11 (and later)
For iOS 11, you must add both the NSLocationWhenInUseUsageDescription key and the NSLocationAlwaysAndWhenInUseUsageDescription key to the Info.plist file.
<key>NSLocationAlwaysUsageDescription</key>
<string>The SDK stores location data locally on the device and
when a geofence event is triggered, the event is sent to MCE,
which a third party. The event does not contain raw location data.
Battery usage is minimal when sharing location.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>The SDK stores location data locally on the device and
when a geofence event is triggered, the event is sent to MCE,
which a third party. The event does not contain raw location data.
Battery usage is minimal when sharing location.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>The SDK stores location data locally on the device and
when a geofence event is triggered, the event is sent to MCE,
which a third party. The event does not contain raw location data.
Battery usage is minimal when sharing location.</string>
Permission requests
iOS 13 changes location permission requests so that users can not grant "always" permission from the application. Location "always" permission can be granted one of two ways.
Note:
DLA requires a minimum API target of 19.
- To use locations, add the following permissions. BLUETOOTH and BLUETOOTH_ADMIN are only needed if you use beacons. On later versions of Android, you also need to request these permissions explicitly from the user. For more information, see Requesting location permissions on Android section.
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
- Add the following services. If you don't use beacons, don't add the last two lines for MceBluetoothScanner and BeaconsVerifier services.
<service android:name="co.acoustic.mobile.push.sdk.location.LocationRetrieveService" /> <service android:name="co.acoustic.mobile.push.sdk.location.LocationEventsIntentService" /> <service android:name="co.acoustic.mobile.push.sdk.location.LocationSyncAlarmListener" /> <receiver android:name="co.acoustic.mobile.push.sdk.location.LocationUpdateCaller" /> <service android:name="co.acoustic.mobile.push.sdk.beacons.MceBluetoothScanner" /> <service android:name="co.acoustic.mobile.push.sdk.beacons.BeaconsVerifier" />
- To enable location support, use the enableLocationSupport method in the co.acoustic.mobile.push.sdk.location.LocationManager class. Do not call this method before the device is registered with the Acoustic Campaign server:
LocationManager.enableLocationSupport(context);
To disable location support, use disableLocationSupport in the co.acoustic.mobile.push.sdk.location.LocationManager class:LocationManager.disableLocationSupport(context);
- To receive location, enter, exit, and dwell events, implement the onLocationEvent method in your subclass of MceBroadcastReceiver. The onLocationEvent is called whenever a geofence or beacon enter, exit, or dwell event occurs. The following code sample shows how to implement the method.
// location type is ibeacon or geofence // location event type is enter, exit or dwell public void onLocationEvent(Context context, MceLocation location, LocationType locationType, LocationEventType locationEventType) { Log.d(TAG, "Location event: "+locationType.name()+" "+locationEventType.name()+" id = "+location.getId()); // Uncomment this line to have the device display its location for testing purposes. // Do not ship with this uncommented. // showNotification(context, locationType.name()+" "+locationEventType.name(), location.getId(), locationType.name()); }
- To receive location, enter, exit, and dwell events when the device location is updated, implement the onLocationUpdate method in your subclass of MceBroadcastReceiver. The onLocationUpdate method is called when the sdk receives a device location update. The following code sample shows how to implement the method.
public void onLocationUpdate(Context context, Location location) { Log.d(TAG, "Location was updated "+location); }
- Add LocationBroadcastReceiver and GeofenceBroadcastReceiver:
<receiver android:name="co.acoustic.mobile.push.sdk.location.LocationBroadcastReceiver" android:enabled="true" android:exported="true"/> <receiver android:name="co.acoustic.mobile.push.sdk.location.GeofenceBroadcastReceiver" android:enabled="true" android:exported="true"/>
- Enable locations in an Activity so that the parent element can display the child permissions dialog.
- Define two different request codes for the permissions incrementally. The codes can be of any value. Example permission request codes to use when requesting permissions
/* * Example permission request codes to use when requesting permissions */ private static final int REQUEST_LOCATION_PERMISSIONS = 0; private static final int REQUEST_BACKGROUND_PERMISSIONS = 1;
- Because Android 11 requires the background location permissions to be requested incrementally after other location permissions, organize permissions into different lists, based on OS version and whether permission requests would be incremental or not.
/* * This is the list of permissions required for DLA for all versions * of Android up to and including Android Pie (API Level 28) */ private static final String[] LOCATION_PERMISSIONS_ANDROID_ALL_VERSION_THROUGH_P = { Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN }; /* * This is the list of permissions required for DLA starting with * Android 10 (API Level 29). Note, the ACCESS_BACKGROUND_LOCATION * permission was added with Android 10. */ (api = Build.VERSION_CODES.Q) private static final String[] LOCATION_PERMISSIONS_ANDROID_Q_OR_LATER = { Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.ACCESS_BACKGROUND_LOCATION }; /* * Starting with Android 11 (API Level 30), the ACCESS_BACKGROUND_LOCATION * permission must be requested separately and after the Fine and Coarse * location permissions have been granted. As such, the ACCESS_BACKGROUND_LOCATION * has been moved to a secondary list. */ (api = Build.VERSION_CODES.R) private static final String[] LOCATION_PERMISSIONS_ANDROID_R_OR_LATER = { Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN }; (api = Build.VERSION_CODES.R) private static final String[] BACKGROUND_PERMISSIONS_ANDROID_R_OR_LATER = { Manifest.permission.ACCESS_BACKGROUND_LOCATION };
- Next, check if the device was running Android 11 or not to make incremental requests.
private void requestPermissions( Activity activity) { /* * Requesting permissions for Android 11 (API Level 30) or later */ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { /* * First check if the primary location permissions have been granted. * Be sure to request these first before ACCESS_BACKGROUND_LOCATION */ if (!checkSelfPermissions(activity, LOCATION_PERMISSIONS_ANDROID_R_OR_LATER)) { ActivityCompat.requestPermissions( activity, LOCATION_PERMISSIONS_ANDROID_R_OR_LATER, REQUEST_LOCATION_PERMISSIONS ); } /* * If the primary location permissions have already been granted, be sure the * ACCESS_BACKGROUND_LOCATION permission has been granted as well. Be sure to * follow Android's recommended practices for requesting location permissions * at run time, seen here: https://developer.android.com/training/location/permissions * This would include displaying to users the rationale for granting this permission * if the operating system deems this necessary. */ else if (!checkSelfPermissions(activity, BACKGROUND_PERMISSIONS_ANDROID_R_OR_LATER)) { if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.ACCESS_BACKGROUND_LOCATION)) { displayBackgroundLocationRationale(); } else { ActivityCompat.requestPermissions( activity, BACKGROUND_PERMISSIONS_ANDROID_R_OR_LATER, REQUEST_BACKGROUND_PERMISSIONS ); } } /* * If all required location permissions have been granted, * you may now enable location support. */ else { LocationManager.enableLocationSupport(getApplicationContext()); updateUIPermissionsGranted(); } } /* * Requesting Location Permissions for Android 10 (API Level 29) and before */ else { /* * Request the correct permissions based on the version of Android */ String[] permissions = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ? LOCATION_PERMISSIONS_ANDROID_Q_OR_LATER : LOCATION_PERMISSIONS_ANDROID_ALL_VERSION_THROUGH_P; if (!checkSelfPermissions(activity, permissions)) { ActivityCompat.requestPermissions( activity, permissions, REQUEST_LOCATION_PERMISSIONS ); } /* * If permissions have already been granted, you may enable location support */ else { LocationManager.enableLocationSupport(getApplicationContext()); updateUIPermissionsGranted(); } } } private boolean checkSelfPermissions( Context context, String[] permissions) { for (String permission : permissions) { if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) { return false; } } return true; }
- Because Android 31 requires Beacon permissions, you must add the BLUETOOTH_SCAN permission for iBeacons to work.
- Check the request code; if it was the request code for the background permissions, and it was granted, then enable location. If it is the request code for the normal location permissions, check the Android version to identify if you need to continue with the incremental permission requests or not.
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { /* * If the request was for ACCESS_BACKGROUND_LOCATIONS and the * permission was granted, enable location support */ if (requestCode == REQUEST_BACKGROUND_PERMISSIONS) { if (checkPermissionsGranted(grantResults)) { LocationManager.enableLocationSupport(getApplicationContext()); updateUIPermissionsGranted(); } } /* * If the request was for the base location permissions, * and the permissions were granted, check the Android version. * If running Android 11 (API Level 30) or later, you may still * need to request the ACCESS_BACKGROUND_LOCATION permission. * Allow the user to grant this permission. For all previous * versions of Android, or if ACCESS_BACKGROUND_LOCATION has * been granted, you may now enable location support. */ else if (requestCode == REQUEST_LOCATION_PERMISSIONS) { if (checkPermissionsGranted(grantResults)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (!checkSelfPermissions(this, BACKGROUND_PERMISSIONS_ANDROID_R_OR_LATER)) { updateUIAccessBackgroundNeeded(); } else { LocationManager.enableLocationSupport(getApplicationContext()); updateUIPermissionsGranted(); } } else { LocationManager.enableLocationSupport(getApplicationContext()); updateUIPermissionsGranted(); } } } } private boolean checkPermissionsGranted( int[] grantResults) { for (int result : grantResults) { if (result != PackageManager.PERMISSION_GRANTED) { return false; } } return true; }
Note:
Android SDK 3.8.6 provides support to meet Google Play’s requirements of API level 31 (Android 12). For applications targetting Android SDK 31, Beacon support requires adding permission to the manifest.
Requesting location permissions on Android
With newer releases of Android, you must explicitly request location permissions. Users can optionally accept or refuse these permissions for the app. On Cordova and Xamarin, the SDK prompts users for permissions by default. However, on native Android, users need to request the permissions.
Note:
For Android 11 (API level 30) or higher, Android now requires that location permissions are requested incrementally. For example, request the foreground locations access first and then the background location access. For more information, see Request location permissions
Our sample app has been updated to enable location permissions incrementally. Check it out to test how it works.
To request permissions for location on Android, do the following:
/*
* Starting with Android 12 (API Level 31), the the new BLUETOOTH_SCAN
* permission must be requested in order for iBeacons to work.
*/
(api = Build.VERSION_CODES.S)
private static final String[] LOCATION_PERMISSIONS_ANDROID_S_OR_LATER = {
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN,
Manifest.permission.BLUETOOTH_SCAN
};
The example code snippets are focused on just requesting permissions and enabling locations. Follow the best practices recommended by Android for implementing the UI for requesting incremental permissions. Those best practices are described here: Request location permissions
Introduce a new dialog
You can also introduce a new dialog to prompt users before opening the Android permissions dialog. In this dialog, you can explain why the user might want to grant permissions and request their consent. If they agree, the Android dialog can be displayed to enable location support. Even if a user does not currently want to share location, you can request it again later when required.
When configuring locations, add the files for the appropriate plug-ins to your project and then register them.
- Add the Location Sync plug-in to Cordova projects by using the Cordova add command from the command line. For example:
'cordova plugin add <path to downloaded directory>/plugins/co.acoustic.mobile.push.plugin.location cordova prepare'
- Add the NSLocation key and string to the info.plist file.
- Start location services by calling the manualLocationInitialization method. If you installed the Cordova plug-in with the AUTO_INITIALIZE_LOCATION variable set to false, you start location services and monitoring for locations by calling the manualLocationInitialization method.
iOS
To configure the Flutter SDK for DLA, you must update the Info.plist file.
- In your project, open the Info.plist file.
- Set the
Background Modes capability
and then enable theLocation updates
and theBackground fetch
.
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>location</string>
</array>
- To use location services, add the NSLocation key and string to the Info.plist file for the app project.
The
NSLocationAlwaysUsageDescription
requests permission from the mobile app user to use location services when your app is running in the background. Because Apple recently started prohibiting apps from sharing location data with third parties when explicit user consent is absent, you might want to useNSLocationAlwaysUsageDescription
to tell users how the SDK uses location data.The SDK stores location data locally on the device, and when an event is triggered, the event is sent to the MCE servers. The event does not contain raw location data. To convince users to grant permission, you might also want to inform them that battery usage is minimal when location sharing is enabled.
<key>NSLocationAlwaysUsageDescription</key>
<string>Permission to use location</string>
iOS 11 (and later)
For iOS 11, you must add both the NSLocationWhenInUseUsageDescription
key and the NSLocationAlwaysAndWhenInUseUsageDescription key to the Info.plist file.
<key>NSLocationAlwaysUsageDescription</key>
<string>Permission to use location</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Permission to use location</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Permission to use location</string>
Permission Requests
iOS 13 changes location permission requests so users can not grant "always" permission from the application. Location "always" permission can be granted one of two ways:
- The app could request the "always" permission and prompt the user to allow for "while using" permission. For example, when the app tries to use location in the background and fails for an unspecified amount of time, the user is asked if the app can have "always" location permission. This request typically occurs while the app is not running or within a day of the original request.
- The app could instruct the user to open the settings app manually, select the app's settings, and then grant the "always" location permission from there.
Android
Update the AndroidManifest.xml file to configure the Flutter SDK for DLA.
- In your project, open the AndroidManifest.xml file.
- To use locations, add the following permissions. BLUETOOTH and BLUETOOTH_ADMIN are only needed if you use beacons. On later versions of Android, you also need to request these permissions explicitly from the user. For more information, see Requesting location permissions on Android section.
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
- Add the following services. If you don't use beacons, don't add the last two lines for MceBluetoothScanner and BeaconsVerifier services.
<service android:name="co.acoustic.mobile.push.sdk.location.LocationRetrieveService" />
<service android:name="co.acoustic.mobile.push.sdk.location.LocationEventsIntentService" />
<service android:name="co.acoustic.mobile.push.sdk.location.LocationSyncAlarmListener" />
<receiver android:name="co.acoustic.mobile.push.sdk.location.LocationUpdateCaller" />
<service android:name="co.acoustic.mobile.push.sdk.beacons.MceBluetoothScanner" />
<service android:name="co.acoustic.mobile.push.sdk.beacons.BeaconsVerifier" />
Choose the right location technology
Location support is provided in the Mobile App Messaging SDK, by default. You can configure location support for geofences and beacons in the Acoustic Mobile App Messaging SDK.
Geofence
If your application can benefit from location-based messaging, and the trigger area is large, unmoving, outside, and predefined, you might want to consider using Geofence technology. Geofences can be a very effective way of sending push messages to users at the right place at the right time. Geofence radii should be large enough to contain the area of interest, including some external area so GPS can be effective. But it should also not be too large as to send messages to users too far away to be interested in them.
Beacon
If your application can benefit from location-based messaging, but the trigger area is either small, inside, small or mobile, you might want to consider using Beacon technology. Beacons can be a very effective way to trigger push messages to your users at the right place at the right time. The events generated can also move users through programs within the Campaign system. If your application is going to use Beacons, you will have to make sure that all the beacons have the same UUID and different detection zones have different sets of major and minor numbers. Use of the same major number should be physically distant from each other. So, if two zones are close to each other, it is best to use different major numbers for each. Keep in mind, you cannot use more then 20 majors across your beacon deployment.
If you want to use beacons or geofences in DLA, you can modify the default settings in the MCEConfig.json file to meet the specific requirements of your app.
When enabled for locations, the Acoustic Mobile App Messaging SDK contacts the Acoustic Campaign server and requests a list of DLA-configured locations (geofences and beacons) that are within a specified distance of the user's current location. The SDK registers the locations with the operating system on the user's device and requests permission from the operating system to receive background location updates about the device's location. The operating system updates the SDK with the user's location at timed intervals, such as every 5 minutes. If the user moves away from the registered locations, the SDK registers a new list of locations that are based on the user's new location. If the user breaches a geofence or beacon, the SDK triggers a location event.
Note:
Before configuring the Mobile App Messaging SDK for geofence and beacon integration, configure locations in the Device Location Awareness (DLA) service. For information, see Device Location Awareness.
Enable DLA for beacons and geofences
The location section of the MCEConfig.json file enables DLA for beacons and geofences in the Mobile App Messaging SDK.
Note:
If you do not want to use beacons or geofences, remove the corresponding section from the MCEConfig.json file. For example, to remove beacon support, remove ibeacon, or to remove geofence support, remove geofence. If you do not want DLA, remove the entire location section.
"location": {
"The location autoInitialize flag can be set to false to delay turning on the location services until desired.": "",
"autoInitialize": true,
"The sync key is only used to customize the iBeacon and Geofence syncing service, it is not required for those features":"",
"sync": {
"Location Sync radius is in meters, default 100km":"",
"syncRadius": 100000,
"Specify how long to wait before syncing again on significant location change in seconds, default 5 minutes":"",
"syncInterval": 300
},
"Please note, the existince of the location key will enable geofence location support, if geofence support is not desired, remove the key":"",
"geofence":{
"choose one of the following values for accuracy: ": ["best", "10m", "100m", "1km", "3km"],
"accuracy": "3km"
},
"Please note, the existince of the ibeacon key will enable iBeacon support, if iBeacon support is not desired, remove the key":"",
"ibeacon": {
"UUID": "SET YOUR IBEACON UUID HERE"
},
},
{ "baseUrl": "https://mobile-sdk-lib-XX-Y.brilliantcollector.com/3.0",
"appKey": {
"prod":"YOUR APP KEY" },
"senderId": "YOUR GCM PROJECT ID",
"sessionsEnabled": true,
"sessionTimeout": 20,
"metricTimeInterval": 30,
"loglevel": "error",
"logfile": false,
"location": {
"request" : {
"interval": 5,
"fastestInterval": 1,
"smallestDisplacement": 100,
"priority": "highAccuracy"
},
"sync": {
"syncInterval": 300,
"syncRadius": 100000,
"locationResponsiveness": 300,
"minLocationsForSearch": 1,
"maxLocationsForSearch": 20, },
"ibeacon": {
"uuid": "YOUR IBEACONS UUID",
"beaconForegroundScanDuration": 5,
"beaconForegroundScanInterval": 30,
"beaconBackgroundScanDuration": 300,
"beaconBackgroundScanInterval": 1800
}
}
}
Campaign provides three location plug-ins that you can use to integrate beacons and geofences with iOS and Android apps developed in Cordova.
The Location Sync plug-in enables location syncing for both beacon and geofence regions. To use either beacons or geofences with apps developed in Cordova, you must first add the Location Sync plug-in to your project.
The Location Sync plug-in uses the SYNC_RADIUS and SYNC_INTERVAL optional parameters for iOS and Android. If you do not include an optional variable in the command line, the default value is used.
- Run the following command line to add the Geofence plug-in to Cordova projects.
'cordova plugin add <path to downloaded directory>/plugins/co.acoustic.mobile.push.plugin.geofence cordova prepare'
- Add the Beacon plug-in to Cordova projects.The Beacon plug-in enables support for beacons in iOS and Android applications developed with Cordova.
To add the Beacon plug-in to your Cordova project, run the following command line, where “8EE07E37-8EE5-4CF4-8A98-992D08285CF0” represents the UUID for your org:
'cordova plugin add <path to downloaded directory>/plugins/co.acoustic.mobile.push.plugin.beacon --variable UUID=8EE07E37-8EE5-4CF4-8A98-992D08285CF0 cordova prepare'
- Install the plugin
Geofence
npm install <sdk folder>/plugins/react-native-acoustic-mobile-push-geofenceBeacon
npm install <sdk folder>/plugins/react-native-acoustic-mobile-push-beacon - Link the plugin
Geofence
react-native link react-native-acoustic-mobile-push-geofenceBeacon
react-native link react-native-acoustic-mobile-push-beacon - Fill out beacon UUID in ‘MceConfig.json’ file. Some beacon vendors use a static UUID others include software to set the UUID on demand. The Acoustic Beacon Plugin uses a single UUID per application, so the beacons UUIDs should all be the same.
- Enter the unique combinations of UUID/major/minor beacons that you will be using on the web interface.
- Enter your geo locations and radii in the web interface.
If you are enabling beacons, you must set the UUID.
iOS
"location": {
"The location autoInitialize flag can be set to false to delay turning on the location services until desired.": "",
"autoInitialize": true,
"Please note, the existince of the location key will enable geofence location support, if geofence support is not desired, remove the key": "",
"geofence": {
"search": {
"maximumRadius": 2500000,
"defaultRadius": 10000,
"maximumCount": 100,
"maximumQuantity": 20,
"minimumQuantity": 1
},
"choose one of the following values for accuracy: ": [
"best",
"10m",
"100m",
"1km",
"3km"
],
"accuracy": "3km"
},
"The sync key is only used to customize the iBeacon and Geofence syncing sevice, it is not required for those features": "",
"sync": {
"Location Sync radius is in meters, default 100km": "",
"syncRadius": 100000,
"Specify how long to wait before syncing again on significant location change in seconds, default 5 minutes": "",
"syncInterval": 300
},
"Please note, the existince of the ibeacon key will enable iBeacon support, if iBeacon support is not desired, remove the key": "",
"ibeacon": {
"UUID": "SET YOUR IBEACON UUID HERE"
}
},
Android
"location": {
"The location autoInitialize flag can be set to false to delay turning on the location services until desired.": "",
"autoInitialize": true,
"The sync key is only used to customize the iBeacon and Geofence syncing service, it is not required for those features": "",
"sync": {
"Specify how long to wait before syncing again on significant location change in seconds, default 5 minutes": "",
"syncInterval": 30,
"Location Sync radius is in meters, default 100km": "",
"syncRadius": 100000,
"Specify how long to wait before retrieving a new location from the device, default 5 minutes": "",
"locationResponsiveness": 300,
"Specify the minimum results when looking for locations nearby, default is 1, minimum value is 1": "",
"minLocationsForSearch": 1,
"Specify the maximum results when looking for locations nearby, default is 20, minimum value is 1": "",
"maxLocationsForSearch": 20,
"Specify the location providers that will be used to retrieve the device location. 'gps' - gps location. 'network' - wifi + cellular, default is gps + network": "",
"providerPreferences": [
"gps",
"network"
]
},
"Please note, unlike iOS, the existence of the ibeacon key does not enable iBeacon support, iBeacon support is enabled only if iBeacons are nearby": "",
"ibeacon": {
"Please note: if this is not set, iBeacon events will not be sent": "This value should never be empty. Leave the default value if you don't want to use iBeacons",
"uuid": "SET YOUR UUID HERE",
"Specify how much time the sdk will scan for iBeacons in every scan session while the application is in the foreground, default is 5 seconds": "",
"beaconForegroundScanDuration": 5,
"Specify how much time the sdk will wait between iBeacons scan sessions while the application is in the foreground, default is 30 seconds": "",
"beaconForegroundScanInterval": 30,
"Specify how much time the sdk will scan for iBeacons in every scan session while the application is in the background, default is 30 seconds": "",
"beaconBackgroundScanDuration": 30,
"Specify how much time the sdk will wait between iBeacons scan sessions while the application is in the background, default is 5 minutes": "",
"beaconBackgroundScanInterval": 30
}
}
Requesting location permissions
With newer releases of Android, you must explicitly request location permissions. Users can optionally accept or refuse these permissions for the app.
For iOS, the location permission will automatically show at the launch of the app after updating Info.plist. However, with changes to iOS 13, location permission requests can no longer grant the permission "always" from the application. One way "always" can be granted would be the app could instruct the user to open the settings app manually, select the app's settings, and then grant the "always" location permission from there.
Complete the following steps to request location permission on Android and prompt the user to grant the "always" location permission on iOS:
- Add the location plugin to the pubspec.yaml file under dependencies.
flutter_acoustic_mobile_push_location
path ../../plugins/flutter_acoustic_mobile_push_location
- Import the location plugin into the dart file.
import 'package:flutter_acoustic_mobile_push_location/location.dart';
- Update the dart file with the following variables.
late LocationsPermission permission;
- Add a location permission function in the dart file.
Future<void> locationPermission() async {
permission = await Location().checkLocationPermission();
if (permission != LocationsPermission.always) {
showPermission();
}
}
- If the permission is other than always, create a dialog to inform the user to update the location permission on their device. The following example also utilizes the location plugin to open the user's system location setting.
showPermission() {
showDialog(
context: context,
useRootNavigator: false,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Permission Required'),
content: const Text(
'This app requires your permission to access location information in the background. To grant this permission, select "Allow all the time" or "Always"'),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context, false);
},
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
Navigator.pop(context, true);
},
child: const Text('Grant Permission'),
),
],
);
},
).then((exit) {
if (exit != null) {
Location().requestDevicePermission(exit);
}
});
}
Customize settings in MceConfig.json
- syncRadius — lets you customize sync services for geofences and beacons. This setting specifies the radius from a site or zone at which the SDK syncs location info with the DLA server. When the mobile device crosses the sync radius, the server downloads a list of site and zone locations to the device for monitoring. A low value causes full sync more frequently. Sync radius is different than the geofence radius that is specified in the DLA UI. The geofence radius specifies the radius at which a location event is triggered. The default value for syncRadius is 100000 meters (100 km).
- syncInterval — lets you customize sync services for geofences and beacons. syncInterval specifies the number of seconds to wait before syncing after a significant location change. The default value for syncInterval is 300 seconds (5 minutes). The minimum allowed value is 300 seconds. If you try to set the syncInterval to a value that is lower than 300 seconds, the SDK resets to 300 seconds.
- accuracy — represents the accuracy assigned to the location manager in the SDK. A higher accuracy setting uses more battery but delivers more accurate location information. A lower accuracy setting uses less battery but delivers less accurate location information. Available accuracy levels include best, 10 m, 100 m, 1 km, and the default 3 km.
- UUID — specifies a UUID for the beacons that you are tracking in locations. The UUID is a randomly generated identifier that beacons emit and devices detect. Generally, an org uses only one UUID and the UUID is unique to the org. In DLA, beacons are identified by a hierarchical combination of a UUID, a major number, and a minor number, where UUID represents a group of associated beacons, such as the beacons associated with an organization.
- autoInitialize — lets you delay activation of location services until you determine that the user wants location services. When you set this value to false, location services are delayed from starting until the SDK's manualLocationInitialization method is called. This value is set to true, by default.
- "locationResponsiveness": 300, — The time interval between location updates from the operating system to the SDK. The lower this interval is set, the more accurate and fast location updates are made, but power consumption is higher. The minimum is 60 seconds (1 minute). Default is 300 seconds (5 minutes).
- "minLocationsForSearch": 1, — The minimum number of geofences and beacons that the search must return. Default is 1.
- "maxLocationsForSearch": 20, — The maximum number of geofences and beacons that the search must return. Default is 20.
- "interval": 5 — Interval between location updates in seconds. Default is 5, minimum is 1.
- "fastestInterval": 1 — The fastest interval between location updates in seconds. Default is 1, the minimum is 1.
- "smallestDisplacement": 100 — The smallest displacement in meters to generate a location request. Overrides the interval values.
- "beaconForegroundScanDuration": 5, — The Bluetooth scan duration when the application is in the foreground. The default is 5 seconds.
- "beaconForegroundScanInterval": 30, — The time between Bluetooth scans when the application is in the foreground. The default is 30 seconds.
- "beaconBackgroundScanDuration": 300, — The Bluetooth scan duration when the application is in the background. The default is 300 seconds (5 minutes).
- "beaconBackgroundScanInterval": 1800, — The time between Bluetooth scans when the application is in the background. The default is 1800 seconds (30 minutes).
Get notified when a geofence or beacon event occurs (Optional)
If you want to be notified when a geofence or beacon event occurs, use NSNotification:
Geofence entry
[[NSNotificationCenter defaultCenter] addObserverForName: EnteredGeofence object:nil queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) {
// use note.userInfo[@"region"].center
// use note.userInfo[@"region"].radius
}];
Geofence exit
[[NSNotificationCenter defaultCenter] addObserverForName: ExitedGeofence object:nil queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) {
// use note.userInfo[@"region"].center
// use note.userInfo[@"region"].radius
}];
Beacon entry
[[NSNotificationCenter defaultCenter] addObserverForName:EnteredBeacon object:nil queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) {
// use note.userInfo[@"minor"]
// use note.userInfo[@"major"]
}];
Beacon exit
[[NSNotificationCenter defaultCenter] addObserverForName:ExitedBeacon object:nil queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) {
// use note.userInfo[@"minor"]
// use note.userInfo[@"major"]
}];
If you want to be notified when geofence or beacon events occur, implement the following delegates:
Geofence entry
MCEGeofencePlugin.setGeofenceEnterCallback(function (geofence) {
// use geofence.latitude
// use geofence.longitude
// use geofence.radius
});
Geofence exit
MCEGeofencePlugin.setGeofenceExitCallback(function (geofence) {
// use geofence.latitude
// use geofence.longitude
// use geofence.radius
});
Beacon entry
MCEBeaconPlugin.setBeaconEnterCallback(function (beacon) {
// use beacon.major
// use beacon.minor
})
Beacon exit
MCEBeaconPlugin.setBeaconExitCallback(function (beacon) {
// use beacon.major
// use beacon.minor
})
Set up foreground service in the background (Android only)
Allows geofence to remain active when your Android app goes into the background (Android 8.0 and higher)
With Android 8, Google introduced background location limits. These have been extended in Android 9. They are intended to allow batteries to last longer than they did on Android 7.1.1. This is accomplished by turning off location services and then turning them on for only brief periods. The result of this Android change is that when your app is in the background, geofences and beacons may be detected irregularly, with significant delays. Our testing revealed at least one case where it took 30 minutes for a geofence to be detected. This can pose a challenge for developers who want to react to location opportunities more quickly. As shipped, the Android SDK attempts to work around background location limits, but is constrained by the OS. Google has provided a few options to keep apps in the foreground and make location detection faster. Some of them, such as installing wallpaper or multiple apps, are not likely to be acceptable to users. The most promising solution appears to be to start a foreground service when your app enters the background, and then terminate that service when your app comes back into the foreground. This has a minor disadvantage: Android will display your app's icon in the notification bar. This is an Android feature intended to let the device user know that your app is running; there is no way to disable this indication. We've implemented the foreground solution below. Use the instructions below to add the appropriate code to your app. This will ensure that geofences and beacons are detected quickly.
Find out more from Google about background location limits here.
- Start a foreground process when your app enters background mode
To prevent your app from being unresponsive to a geofence, or a beacon enter/exit event, you can start a foreground process whenever the app enters background mode. This stops the process when the app comes to the foreground. This has the side-effect of leaving a visual indicator that your app is running in the status bar.
Detection of the application state change is typically handled by a class that implements the Application.ActivityLifecycleCallbacks. An example that works with the sample app can be found in ApplicationLifecycleHandler.java.
- Register the lifecycle handler in the application
The lifecycle handler must be registered in the application. This can be done by adding the following code to the end of the onCreate method:
ApplicationLifecycleHandler handler = new ApplicationLifecycleHandler();
registerActivityLifecycleCallbacks(handler);
registerComponentCallbacks(handler);
handler.setContext(this);
- Add the NotificationChannel
The foreground service will need its own NotificationChannel, which should be added to the end of createNotificationChannel():
// create a separate notification channel for the service notification
NotificationChannel serviceChannel = notificationManager.getNotificationChannel(MCE_SERVICE_CHANNEL_ID);
if(serviceChannel == null) {
CharSequence name = "WCA SDK Service Channel";
String description = "This keeps the app alive for geofence detection";
int importance = NotificationManager.IMPORTANCE_LOW;
serviceChannel = new NotificationChannel(MCE_SERVICE_CHANNEL_ID, name, importance);
channel.setDescription(description);
notificationManager.createNotificationChannel(serviceChannel);
}
- Add a foreground service
ForegroundService.java
package co.acoustic.wca.samples.android;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import android.support.v4.app.NotificationCompat;
public class ForegroundService extends Service {
private static final String LOG_TAG = "ForegroundService";
static final String START_ACTION = "FOREGROUND_START_ACTION";
static final String STOP_ACTION = "FOREGROUND_STOP_ACTION";
static final String MAIN_ACTION = "FOREGROUND_MAIN_ACTION";
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(LOG_TAG, "onStartCommand");
String action = intent.getAction();
if(action == null) {
return START_STICKY;
}
if (action.equals(START_ACTION)) {
Log.i(LOG_TAG, "Received Start Foreground Intent ");
Intent notificationIntent = new Intent(this, MainSampleMenuActivity.class);
notificationIntent.setAction(MAIN_ACTION);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(this, SampleApplication.MCE_SERVICE_CHANNEL_ID)
.setContentTitle("(your application name)")
.setContentText("Listening for geofences/beacons...")
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(pendingIntent)
.setOngoing(true)
.build();
startForeground(1, notification);
}
if (action.equals(STOP_ACTION)) {
Log.i(LOG_TAG, "Received Stop Foreground Intent");
stopForeground(true);
stopSelf();
}
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
// Used only in case of bound services.
return null;
}
}
- Update the AndroidManifest.xml
Finally, you'll need to update the AndroidManifest.xml to give appropriate permissions to the ForegroundService:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
...
<service
android:name="co.acoustic.wca.samples.android.ForegroundService">
</service>
- New and modified files for the sample app
SampleApplication.java
/*
* Licensed Materials - Property of Acoustic
*
* 5725E28, 5725I03
*
* © Copyright Acoustic. 2011, 2016
* US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with Acoustic.
*/
package co.acoustic.wca.samples.android;
import android.annotation.TargetApi;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.util.Log;
import co.acoustic.wca.samples.android.layout.ResourcesHelper;
import co.acoustic.mobile.push.sdk.api.MceApplication;
import co.acoustic.mobile.push.sdk.api.MceSdk;
import co.acoustic.mobile.push.sdk.api.notification.NotificationsPreference;
import co.acoustic.mobile.push.sdk.registration.RegistrationClientImpl;
public class SampleApplication extends MceApplication {
public static final String MCE_SAMPLE_NOTIFICATION_CHANNEL_ID = "mce_sample_channel";
public static final String MCE_SERVICE_CHANNEL_ID = "mce_service_channel";
@Override
public void onCreate() {
super.onCreate();
Log.d("VersionTest", "v = "+RegistrationClientImpl.getVersion(getApplicationContext()));
ResourcesHelper resourcesHelper = new ResourcesHelper(getResources(), getPackageName());
/**
* Custom layout
*/
MceSdk.getNotificationsClient().setCustomNotificationLayout(this,
resourcesHelper.getString("expandable_layout_type"),
resourcesHelper.getLayoutId("custom_notification"),
resourcesHelper.getId("bigText"),
resourcesHelper.getId("bigImage"), resourcesHelper.getId("action1"),
resourcesHelper.getId("action2"),
resourcesHelper.getId("action3"));
MceSdk.getNotificationsClient().getNotificationsPreference().setSoundEnabled(getApplicationContext(), true);
MceSdk.getNotificationsClient().getNotificationsPreference().setSound(getApplicationContext(), resourcesHelper.getRawId("notification_sound"));
MceSdk.getNotificationsClient().getNotificationsPreference().setVibrateEnabled(getApplicationContext(), true);
long[] vibrate = { 0, 100, 200, 300 };
MceSdk.getNotificationsClient().getNotificationsPreference().setVibrationPattern(getApplicationContext(), vibrate);
MceSdk.getNotificationsClient().getNotificationsPreference().setIcon(getApplicationContext(),resourcesHelper.getDrawableId("icon"));
MceSdk.getNotificationsClient().getNotificationsPreference().setLightsEnabled(getApplicationContext(), true);
int ledARGB = 0x00a2ff;
int ledOnMS = 300;
int ledOffMS = 1000;
MceSdk.getNotificationsClient().getNotificationsPreference().setLights(getApplicationContext(), new int[]{ledARGB, ledOnMS, ledOffMS});
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel(getApplicationContext());
}
ApplicationLifecycleHandler handler = new ApplicationLifecycleHandler();
registerActivityLifecycleCallbacks(handler);
registerComponentCallbacks(handler);
handler.setContext(this);
}
private static final String PREFS_NAME = "ACOUSTIC_MCE_SAMPLE";
private static SharedPreferences getSharedPref(Context context) {
return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
}
private static SharedPreferences.Editor getEditor(Context context) {
return getSharedPref(context).edit();
}
@TargetApi(26)
private static void createNotificationChannel(Context context) {
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel = notificationManager.getNotificationChannel(MCE_SAMPLE_NOTIFICATION_CHANNEL_ID);
if(channel == null) {
CharSequence name = context.getString(R.string.notif_channel_name);
String description = context.getString(R.string.notif_channel_description);
int importance = NotificationManager.IMPORTANCE_HIGH;
channel = new NotificationChannel(MCE_SAMPLE_NOTIFICATION_CHANNEL_ID, name, importance);
channel.setDescription(description);
NotificationsPreference notificationsPreference = MceSdk.getNotificationsClient().getNotificationsPreference();
notificationsPreference.setNotificationChannelId(context, MCE_SAMPLE_NOTIFICATION_CHANNEL_ID);
notificationManager.createNotificationChannel(channel);
}
// FOREGROUNDSERVICE: Added to support foreground service to keep geofences alive
// create a separate notification channel for the service notification
NotificationChannel serviceChannel = notificationManager.getNotificationChannel(MCE_SERVICE_CHANNEL_ID);
if(serviceChannel == null) {
CharSequence name = "WCA SDK Service Channel";
String description = "This keeps the app alive for geofence detection";
int importance = NotificationManager.IMPORTANCE_LOW;
serviceChannel = new NotificationChannel(MCE_SERVICE_CHANNEL_ID, name, importance);
channel.setDescription(description);
notificationManager.createNotificationChannel(serviceChannel);
}
}
}
Updated almost 2 years ago