setMockMode doesn't work - FusedLocationApi - android

I am setting mock location (using FusedLocationApi) for testing my application based on Maps but it doesn't seem to work.
LocationServices.FusedLocationApi.setMockMode(mGoogleApiClient, true);
Location location = new Location("fused");
// Time is needed to create a valid Location
long currentTime = System.currentTimeMillis();
long elapsedTimeNanos = 0;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
elapsedTimeNanos = SystemClock.elapsedRealtimeNanos();
location.setElapsedRealtimeNanos(elapsedTimeNanos);
}
location.setTime(currentTime);
// new york 40.7128° N, 74.0059° W
location.setLatitude(40.7128);
location.setLongitude(74.0059);
PendingResult<Status> s = LocationServices.FusedLocationApi.setMockLocation(mGoogleApiClient, location);
s.setResultCallback(new ResolvingResultCallbacks<Status>(MainActivity.this, 100) {
#Override
public void onSuccess(#NonNull Status status) {
if(status.isSuccess()){
log("mock success");
if (mIsFromContinuousUpdates) {
startContinuousUpdates();
return;
}
getLastKnowLocation();
}
}
#Override
public void onUnresolvableFailure(#NonNull Status status) {
}
});
PendingResult returns success but when I call getLastKnownLocation it returns null and onLocationChanged is not triggered when I request continuous updates.
Am I missing anything here?

when starting location updates, it should receive an pendingIntent that implemnts OnHandleIntent, which extracts the location from the Intent parameter
public class LocationUpdateActivity extends IntentService implements LocationListener {
private static final String TAG = LocationUpdateActivity .class.getSimpleName();
private Location mCurrentLocation;
public LocationUpdateActivity(String name) {
super(name);
}
public LocationUpdateActivity() {
super("LocationUpdateActivity");
}
#Override
protected void onHandleIntent(#Nullable Intent intent) {
Log.i(TAG, "onHandleIntent");
if (LocationResult.hasResult(intent)) {
LocationResult locationResult = LocationResult.extractResult(intent);
mCurrentLocation = locationResult.getLastLocation();
if (mCurrentLocation != null) {
Log.d(TAG, "accuracy: " + mCurrentLocation.getAccuracy() + " lat: " + mCurrentLocation.getLatitude() + " lon: " + mCurrentLocation.getLongitude());
}
}
}
And setting the updates:
Intent locationIntent = new Intent(mContext, LocationUpdateActivity.class);
PendingIntent locationPendingIntent = PendingIntent.getService(
mContext,
0,
locationIntent,
PendingIntent.FLAG_UPDATE_CURRENT
);
FusedLocationApi.requestLocationUpdates(
mGoogleApiClient, mLocationRequest, locationPendingIntent);

Related

Background location update in Android periodically

I am Using Fused Location Provider Google api client to get the location for every 5 minutes . It is updating both in background and also when the application is killed . But it is Stopped updating after some 2 to 3 days . i have tested in most of the android devices.
I am calling this Service from an Activity and i am Killing the application, it is sending location updates for every 5 minutes for almost 3 days after that it is getting stopped .I tried So many times.
public class GPSTracker extends Service implements
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener,
com.google.android.gms.location.LocationListener {
protected static final String TAG = "Location...";
private Context mContext = this;
/**
* Tracks the status of the location updates request.
*/
public static Boolean mRequestingLocationUpdates;
/**
* Time when the location was updated represented as a String.
*/
protected String mLastUpdateTime;
/**
* Provides the entry point to Google Play services.
*/
protected GoogleApiClient mGoogleApiClient;
/**
* Stores parameters for requests to the FusedLocationProviderApi.
*/
protected LocationRequest mLocationRequest;
/**
* Represents a geographical location.
*/
protected Location mCurrentLocation;
public static boolean isEnded = false;
#Override
public void onCreate() {
super.onCreate();
buildGoogleApiClient();
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("LOC", "Service init...");
isEnded = false;
mRequestingLocationUpdates = false;
mLastUpdateTime = "";
// buildGoogleApiClient();
if (mGoogleApiClient.isConnected() && mRequestingLocationUpdates) {
startLocationUpdates();
}
return START_STICKY;
}
#Override
public void onConnected(Bundle bundle) {
startLocationUpdates();
}
#Override
public void onConnectionSuspended(int i) {
// The connection to Google Play services was lost for some reason. We call connect() to
// attempt to re-establish the connection.
Log.i(TAG, "Connection suspended==");
mGoogleApiClient.connect();
}
#Override
public void onLocationChanged(Location location) {
mCurrentLocation = location;
mLastUpdateTime = DateFormat.getTimeInstance().format(new Date());
double latitude = location.getLatitude();
double longitude = location.getLongitude();
StringBuilder stringBuilder = new StringBuilder();
StringBuilder latlong = stringBuilder.append(latitude + "," + longitude);
Calendar cal = Calendar.getInstance();
String zone = TimeZone.getDefault().getDisplayName(false, android.icu.util.TimeZone.SHORT);
SimpleDateFormat formatter = new SimpleDateFormat("dd-MMM 'at' h:mm a");
Date date = new Date();
String localtime = (formatter.format(date)).toString();
Date myDate = new Date();
Calendar calendar = Calendar.getInstance();
calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
calendar.setTime(myDate);
date = calendar.getTime();
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss zz");
dateFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
String utc = dateFormatter.format(date);
// DateFormat df = DateFormat.getTimeInstance();
// df.setTimeZone(TimeZone.getTimeZone("gmt"));
// String gmtTime = df.format(new Date());
String model = Build.MODEL;
String reqString = Build.VERSION.RELEASE
+ " " + Build.VERSION_CODES.class.getFields()[android.os.Build.VERSION.SDK_INT].getName();
// Date currentTime = Calendar.getInstance().getTime();
// String localtime = currentTime.toString();
PreferencesClass preferencesClass = new PreferencesClass(mContext);
String id = preferencesClass.getFingerPrint();
Data data = null;
try {
JSONObject jsonObject = new JSONObject();
jsonObject.put("userId", id);
jsonObject.put("latLon", latlong);
jsonObject.put("timeZone", zone);
jsonObject.put("localTime", localtime);
jsonObject.put("osVersion",reqString);
jsonObject.put("phoneModel",model);
jsonObject.put("utcTime",utc);
data = new Data();
data.setData(jsonObject.toString());
if (data != null) {
if (Utility.isConnectingToInternet(mContext)) {
// boolean isChecked = preferencesClass.getIsChecked();
// if (isChecked){
LocationWebServiceMgr locationWebServiceMgr = new LocationWebServiceMgr();
locationWebServiceMgr.Location(data, new CallBackInterface() {
#Override
public void onResponse(ArrayList<Object> objects, ResponseMetaData responseMetaData) {
Log.d(TAG, "onResponse: Succesfully added the location to server");
// Toast.makeText(getApplicationContext(), "added to server", Toast.LENGTH_LONG).show();
}
#Override
public void onFailure(ResponseMetaData t) {
}
});
// } else {
// Log.d("serverCall", "Location Permission not available ");
// }
} else {
Log.e("serverCall", "Network not available");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
// Refer to the javadoc for ConnectionResult to see what error codes might be returned in
// onConnectionFailed.
Log.i(TAG, "Connection failed: ConnectionResult.getErrorCode() = " + connectionResult.getErrorCode());
}
/**
* Builds a GoogleApiClient. Uses the {#code #addApi} method to request the
* LocationServices API.
*/
protected synchronized void buildGoogleApiClient() {
Log.i(TAG, "Building GoogleApiClient===");
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
createLocationRequest();
}
/**
* Sets up the location request. Android has two location request settings:
* {#code ACCESS_COARSE_LOCATION} and {#code ACCESS_FINE_LOCATION}. These settings control
* the accuracy of the current location. This sample uses ACCESS_FINE_LOCATION, as defined in
* the AndroidManifest.xml.
* <p/>
* When the ACCESS_FINE_LOCATION setting is specified, combined with a fast update
* interval (5 seconds), the Fused Location Provider API returns location updates that are
* accurate to within a few feet.
* <p/>
* These settings are appropriate for mapping applications that show real-time location
* updates.
*/
protected void createLocationRequest() {
mGoogleApiClient.connect();
mLocationRequest = new LocationRequest();
// Sets the desired interval for active location updates. This interval is
// inexact. You may not receive updates at all if no location sources are available, or
// you may receive them slower than requested. You may also receive updates faster than
// requested if other applications are requesting location at a faster interval.
mLocationRequest.setInterval(Constants.UPDATE_INTERVAL_IN_MILLISECONDS);
// Sets the fastest rate for active location updates. This interval is exact, and your
// application will never receive updates faster than this value.
mLocationRequest.setFastestInterval(Constants.FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);
// mLocationRequest.setSmallestDisplacement(Constants.DISPLACEMENT);
//mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
}
/**
* Requests location updates from the FusedLocationApi.
*/
protected void startLocationUpdates() {
if (!mRequestingLocationUpdates) {
mRequestingLocationUpdates = true;
// The final argument to {#code requestLocationUpdates()} is a LocationListener
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
LocationServices.FusedLocationApi.requestLocationUpdates(
mGoogleApiClient, mLocationRequest, this);
Log.i(TAG, " startLocationUpdates===");
isEnded = true;
}
}
}
you can create service for do this
public class TimeService extends Service {
// constant
public static final long NOTIFY_INTERVAL = 10 * 1000; // 10 seconds
// run on another Thread to avoid crash
private Handler mHandler = new Handler();
// timer handling
private Timer mTimer = null;
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
// cancel if already existed
if(mTimer != null) {
mTimer.cancel();
} else {
// recreate new
mTimer = new Timer();
}
// schedule task
mTimer.scheduleAtFixedRate(new TimeDisplayTimerTask(), 0, NOTIFY_INTERVAL);
}
class TimeDisplayTimerTask extends TimerTask {
#Override
public void run() {
// run on another thread
mHandler.post(new Runnable() {
#Override
public void run() {
// display toast
startService(new Intent(this, MyService.class));
}
});
}
private String getDateTime() {
// get date time in custom format
SimpleDateFormat sdf = new SimpleDateFormat("[yyyy/MM/dd - HH:mm:ss]");
return sdf.format(new Date());
}
}
and service for location
public class MyService extends Service
{
private static final String TAG = "BOOMBOOMTESTGPS";
private LocationManager mLocationManager = null;
private static final int LOCATION_INTERVAL = 1000;
private static final float LOCATION_DISTANCE = 10f;
private class LocationListener implements android.location.LocationListener
{
Location mLastLocation;
public LocationListener(String provider)
{
Log.e(TAG, "LocationListener " + provider);
mLastLocation = new Location(provider);
}
#Override
public void onLocationChanged(Location location)
{
Log.e(TAG, "onLocationChanged: " + location);
mLastLocation.set(location);
}
#Override
public void onProviderDisabled(String provider)
{
Log.e(TAG, "onProviderDisabled: " + provider);
}
#Override
public void onProviderEnabled(String provider)
{
Log.e(TAG, "onProviderEnabled: " + provider);
}
#Override
public void onStatusChanged(String provider, int status, Bundle extras)
{
Log.e(TAG, "onStatusChanged: " + provider);
}
}
LocationListener[] mLocationListeners = new LocationListener[] {
new LocationListener(LocationManager.GPS_PROVIDER),
new LocationListener(LocationManager.NETWORK_PROVIDER)
};
#Override
public IBinder onBind(Intent arg0)
{
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId)
{
Log.e(TAG, "onStartCommand");
super.onStartCommand(intent, flags, startId);
return START_STICKY;
}
#Override
public void onCreate()
{
Log.e(TAG, "onCreate");
initializeLocationManager();
try {
mLocationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER, LOCATION_INTERVAL, LOCATION_DISTANCE,
mLocationListeners[1]);
} catch (java.lang.SecurityException ex) {
Log.i(TAG, "fail to request location update, ignore", ex);
} catch (IllegalArgumentException ex) {
Log.d(TAG, "network provider does not exist, " + ex.getMessage());
}
try {
mLocationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, LOCATION_INTERVAL, LOCATION_DISTANCE,
mLocationListeners[0]);
} catch (java.lang.SecurityException ex) {
Log.i(TAG, "fail to request location update, ignore", ex);
} catch (IllegalArgumentException ex) {
Log.d(TAG, "gps provider does not exist " + ex.getMessage());
}
}
#Override
public void onDestroy()
{
Log.e(TAG, "onDestroy");
super.onDestroy();
if (mLocationManager != null) {
for (int i = 0; i < mLocationListeners.length; i++) {
try {
mLocationManager.removeUpdates(mLocationListeners[i]);
} catch (Exception ex) {
Log.i(TAG, "fail to remove location listners, ignore", ex);
}
}
}
}
private void initializeLocationManager() {
Log.e(TAG, "initializeLocationManager");
if (mLocationManager == null) {
mLocationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
}
}
}
Use workmanager to reschedule your location service. Check if location service not running then start from scheduler.
To read more regarding workmanager click here
Create a class for location get.
private class LocationListener implements android.location.LocationListener
{
Location mLastLocation;
public LocationListener(String provider)
{
Log.e(TAG, "LocationListener " + provider);
mLastLocation = new Location(provider);
}
#Override
public void onLocationChanged(Location location)
{
Log.e(TAG, "onLocationChanged: " + location);
mLastLocation.set(location);
}
#Override
public void onProviderDisabled(String provider)
{
Log.e(TAG, "onProviderDisabled: " + provider);
}
#Override
public void onProviderEnabled(String provider)
{
Log.e(TAG, "onProviderEnabled: " + provider);
}
#Override
public void onStatusChanged(String provider, int status, Bundle extras)
{
Log.e(TAG, "onStatusChanged: " + provider);
}
}
in onLocation(Location location) change method you will get current let,long
For that, you can use AlarmManager or firebase jobdispatcher or job scheduler to trigger any event after specific intervals. I suggest you to use AlarmManager which are easiest.
Refer this code to set alarm:
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
int notificationID = 2018;
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(this, AlarmReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, notificationID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),5*1000, pendingIntent);
And you can cancel alarm like this way:
AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(this, AlarmReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, notificationID, intent, PendingIntent.FLAG_CANCEL_CURRENT);
alarmManager.cancel(pendingIntent);
pendingIntent.cancel();
Note: Beginning with API 19 (KITKAT) alarm delivery is inexact: the OS will shift alarms in order to minimize wakeups and battery use. There are new APIs to support applications which need strict delivery guarantees; see setWindow(int, long, long, PendingIntent) and setExact(int, long, PendingIntent). Applications whose targetSdkVersion is earlier than API 19 will continue to see the previous behavior in which all alarms are delivered exactly when requested.

Android Geofence works after phone restart

I am adding multiple geofence to one app.(Max 100 allowed according to documentation.) I was getting callbacks of geofence then it stopped.
Geofence works smoothly only when I restart phone. After few minutes it stops working again.
public class GeoFenceHelper implements IGeoFenceListener, GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener {
private Context context;
private GeofencingClient mGeoFencingClient;
private PendingIntent geoFencePendingIntent;
private String TAG = GeoFenceHelper.class.getSimpleName();
private GoogleApiClient mGoogleApiClient;
private Location mLastLocation;
Location tempLocation = new Location("");
/**
* The list of geoFences used in this sample.
*/
private ArrayList<Geofence> mGeoFenceList;
private List<GeoFenceModel> geoFenceEntries;
private ArrayList<String> removeGeoFenceList = new ArrayList<>();
public GeoFenceHelper(Context context) {
this.context = context;
mGeoFenceList = new ArrayList<>();
mGeoFencingClient = LocationServices.getGeofencingClient(context);
}
public void registerGeoFence(List<GeoFenceModel> geoFenceEntries) {
mGeoFenceList.clear();
this.geoFenceEntries = geoFenceEntries;
if (DatabaseManager.getInstance(context).getGeoFenceLocationCount() <= Constants.MAX_GEOFENCE_ENTRY_COUNT) {
addGeoFences(geoFenceEntries);
} else {
buildGoogleApiClient();
}
}
private void addGeoFences(List<GeoFenceModel> geoFenceEntries) {
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
for (GeoFenceModel geoFenceEntry : geoFenceEntries) {
if(geoFenceEntry.getId()==Constants.DESTINATION_REACHED_ID) {
removeGeoFenceList.clear();
removeGeoFenceList.add(""+geoFenceEntry.getId());
unregisterGeoFence(removeGeoFenceList);
}
Geofence geoFence = new Geofence.Builder()
.setRequestId(String.valueOf(geoFenceEntry.getId()))
.setExpirationDuration(geoFenceEntry.getExpireDuration())
.setCircularRegion(
geoFenceEntry.getLatitude(),
geoFenceEntry.getLongitude(),
geoFenceEntry.getRadius()
)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT)
.build();
CustomLogger.logE("GEOGENCE", "Geo Fence lat = " + geoFenceEntry.getLatitude() + " log = " + geoFenceEntry.getLongitude() + "Radius = " + geoFenceEntry.getRadius()+" Expiry "+geoFenceEntry.getExpireDate());
mGeoFenceList.add(geoFence);
}
mGeoFencingClient.addGeofences(getGeoFencingRequest(mGeoFenceList), getGeoFencePendingIntent());
Utils.stopLocationUpdate(context);
}
#Override
public void unregisterGeoFence(List<String> genFenceList) {
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
CustomLogger.logE("GeoFence","Remove GeoFence "+genFenceList.get(0));
mGeoFencingClient.removeGeofences(genFenceList);
}
/**
* Builds and returns a GeoFencingRequest. Specifies the list of geoFences to be monitored.
* Also specifies how the geoFence notifications are initially triggered.
*
* #param mGeoFenceList
*/
private GeofencingRequest getGeoFencingRequest(ArrayList<Geofence> mGeoFenceList) {
GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
builder.addGeofences(mGeoFenceList);
return builder.build();
}
private PendingIntent getGeoFencePendingIntent() {
// Reuse the PendingIntent if we already have it.
if (geoFencePendingIntent != null) {
return geoFencePendingIntent;
}
Intent intent = new Intent(context, GeofenceTransitionsIntentService.class);
geoFencePendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
return geoFencePendingIntent;
}
//Location related code
private synchronized void buildGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(this.context)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API).build();
if (mGoogleApiClient != null && !mGoogleApiClient.isConnected()) {
mGoogleApiClient.connect();
}
}
#Override
public void onConnected(#Nullable Bundle bundle) {
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
if (mLastLocation != null) {
double latitude = mLastLocation.getLatitude();
double longitude = mLastLocation.getLongitude();
CustomLogger.logE("tempLocation", "lat " + latitude + "long " + longitude);
List<GeoFenceModel> geoFenceLocations = DatabaseManager.getInstance(context).getGeoFenceLocation();
calculateLocationDistance(mLastLocation, geoFenceLocations);
try {
deleteLocations(geoFenceLocations);
addGeoFences(geoFenceEntries);
} catch (SQLException e) {
e.printStackTrace();
}
} else {
CustomLogger.logE("tempLocation", "(Couldn't get the tempLocation. Make sure tempLocation is enabled on the device)");
}
}
private void deleteLocations(List<GeoFenceModel> geoFenceLocations) throws SQLException {
int reduceCount = Math.abs(Constants.MAX_GEOFENCE_ENTRY_COUNT - geoFenceLocations.size());
CustomLogger.logE("GEOGENCE", "Geo Fence App lat = "+geoFenceLocations.get(0).getLatitude() + " log = "+ geoFenceLocations.get(0).getLongitude() + "App sepcific = "+ geoFenceLocations.get(0).isAppSpecific());
GeoFenceModel geoFenceModel = null;
for (int i = 0; i < reduceCount; i++) {
if(geoFenceLocations.get(0).isAppSpecific())
{
geoFenceModel = geoFenceLocations.remove(1); //because app specific item will be available at top of the list
}
else
{
geoFenceModel = geoFenceLocations.remove(0);
}
DatabaseManager.getInstance(context).getHelper().getNotificationGeoFenceModelDao().delete(geoFenceModel);
try {
List notificationIds = new ArrayList<String>();
notificationIds.add(String.valueOf(geoFenceModel.getId()));
unregisterGeoFence(notificationIds);
}
catch (Exception e)
{
CustomLogger.logE("GEOFENCE ","EXCEPTION"+e);
}
for (GeoFenceModel geoFence: geoFenceEntries) {
if (geoFence.getId()==geoFenceModel.getId())
{
geoFenceEntries.remove(geoFenceModel);
CustomLogger.logE("GEOGENCE", "Geo Fence Removed lat = "+geoFence.getLatitude() + " log = "+ geoFence.getLongitude() + "Radius = "+ geoFence.getRadius());
}
}
}
}
private void calculateLocationDistance(Location mLastLocation, List<GeoFenceModel> geoFenceLocations) {
for (GeoFenceModel geoFenceLocation : geoFenceLocations) {
tempLocation.setLatitude(geoFenceLocation.getLatitude());
tempLocation.setLongitude(geoFenceLocation.getLongitude());
geoFenceLocation.setDistanceInMeters(mLastLocation.distanceTo(tempLocation));
}
Collections.sort(geoFenceLocations);
}
#Override
public void onConnectionSuspended(int i) {
}
#Override
public void onConnectionFailed(#NonNull ConnectionResult connectionResult) {
}
In logs, geofence is getting register properly.
I have implemented IntentService in pending intent. I tried broadcast receiver also but result was same.
Tried same code with multiple devices and OS version.
Location updates on interval is missing in your code.
You have to request for location at HIGH_ACCURACY which enables GPS to give you accurate location at given interval time.
There are lot of changes required as per this tutorial.
go through above tutorial and let me know if it doesn't work.
You must have to create location request.
one more tutorial

how to update latitude and longitude through a background service at regular intervals?

I have written a background service to insert latitude , longitude and some other details into my sqlite database based on the time limit set in my AlarmManager.
I am using GoogleApiClient to calculate latitude and longitude , but i am facing some problems.
Problem
When i am travelling outside without net , the latitude and longitude is not getting updated at regular intervals. The data is getting inserted in my sqlite at regular intervals but latitude and longitude remains same even after half an hour though i am travelling some about 10 kms.
I want the latitude and longitude to change while i am travelling like auto update.
I wanted to know if the FusedLocationApi calculates latitude longitude properly even if i am not connected to the internet , if yes then i need some suggestions to improve the code that i have tried.
Code i have tried.
AlarmManagerService.java
public class AlarmManagerService extends Service implements
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener, LocationListener {
private static final String TAG = "AlarmManagerService";
AlarmReceiver alarmReceiver;
DriverDbHelper driverDbHelper;
Handler handler;
private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 1000;
private Location mLastLocation;
// Google client to interact with Google API
private GoogleApiClient mGoogleApiClient;
// boolean flag to toggle periodic location updates
private boolean mRequestingLocationUpdates = false;
private LocationRequest mLocationRequest;
// Location updates intervals in sec
private static int UPDATE_INTERVAL = 120000; // 2 mins
private static int FATEST_INTERVAL = 60000; // 1 min
private static int DISPLACEMENT = 10; // 10 meters
public Activity activity;
protected String mLastUpdateTime;
String areaName0, areaName1, areaName2, areaName3, fullAreaName,
sessionMobileNo, sessionDriverName, sessionDID, sessiondriverUserId,
date, time, dateTime, doc, trac_transId;
double latitude, longitude;
PowerManager.WakeLock mWakeLock;
SessionManager session;
ConnectionDetector cd;
Boolean isInternet = false;
String sync_no = "No";
String sync_yes = "Yes";
public AlarmManagerService() {
alarmReceiver = new AlarmReceiver();
driverDbHelper = new DriverDbHelper(this);
cd = new ConnectionDetector(this);
}
public void setActivity(Activity activity) {
this.activity = activity;
}
#Override
public void onCreate() {
super.onCreate();
try {
driverDbHelper.open(AlarmManagerService.this);
} catch (Exception e) {
e.printStackTrace();
}
Log.d("Service created", "Alarm Service Created");
// First we need to check availability of play services
if (checkPlayServices()) {
// Building the GoogleApi client
buildGoogleApiClient();
createLocationRequest();
}
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
PowerManager mgr = (PowerManager)
getSystemService(Context.POWER_SERVICE);
if (this.mWakeLock == null) {
this.mWakeLock = mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"MyWakeLock");
}
if (!this.mWakeLock.isHeld()) {
this.mWakeLock.acquire();
}
if (mGoogleApiClient != null) {
mGoogleApiClient.connect();
}
Log.d("Service started", "Alarm Service Started");
handler = new Handler(Looper.myLooper());
handler.post(new Runnable() {
#Override
public void run() {
postDatabaseDetails();
}
});
return START_STICKY;
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onDestroy() {
super.onDestroy();
Log.d("Service started", "Alarm Service Destroyed");
if (mGoogleApiClient.isConnected()) {
stopLocationUpdates();
mGoogleApiClient.disconnect();
}
if (this.mWakeLock != null) {
this.mWakeLock.release();
this.mWakeLock = null;
}
}
private void displayLocation() {
if (mLastLocation != null) {
latitude = mLastLocation.getLatitude();
longitude = mLastLocation.getLongitude();
List<Address> addressList = null;
try {
Geocoder gcd = new Geocoder(getBaseContext(),
Locale.getDefault());
addressList = gcd.getFromLocation(mLastLocation.getLatitude(),
mLastLocation.getLongitude(), 1);
if (addressList != null && addressList.size() > 0) {
Address address = addressList.get(0);
areaName0 = address.getLocality(); // city name
areaName1 = address.getSubLocality();
areaName2 = address.getAdminArea();// statename
//areaName3 = address.getFeatureName(); plot no
//areaName3 = address.getCountryCode();// IN
areaName3 = address.getThoroughfare();
fullAreaName = areaName3 + "\n" + areaName1 + "\n" +
areaName0 + "," + areaName2;
} else {
Log.i("Location ", "Location null");
}
} catch (IOException e1) {
Log.e("HomePage", "IO Exception in getFromLocation()");
e1.printStackTrace();
} catch (IllegalArgumentException e2) {
// Error message to post in the log
String errorString = "Illegal arguments " +
Double.toString(mLastLocation.getLatitude()) + " , " +
Double.toString(mLastLocation.getLongitude()) +
" passed to address service";
Log.e("HomePage", errorString);
e2.printStackTrace();
}
}
}
/**
* Creating google api client object
*/
protected synchronized void buildGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API).build();
}
/**
* Creating location request object
*/
protected void createLocationRequest() {
mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(UPDATE_INTERVAL);
//mLocationRequest.setFastestInterval(FATEST_INTERVAL);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mLocationRequest.setSmallestDisplacement(DISPLACEMENT);
}
/**
* Method to verify google play services on the device
*/
private boolean checkPlayServices() {
int resultCode = GooglePlayServicesUtil
.isGooglePlayServicesAvailable(this);
if (resultCode != ConnectionResult.SUCCESS) {
if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
GooglePlayServicesUtil.getErrorDialog(resultCode, activity,
PLAY_SERVICES_RESOLUTION_REQUEST).show();
} else {
Log.i("Google play services", "Device not supported");
activity.finish();
}
return false;
}
return true;
}
/**
* Starting the location updates
*/
protected void startLocationUpdates() {
LocationServices.FusedLocationApi.requestLocationUpdates(
mGoogleApiClient, mLocationRequest, this);
}
/**
* Stopping location updates
*/
protected void stopLocationUpdates() {
LocationServices.FusedLocationApi.removeLocationUpdates(
mGoogleApiClient, this);
}
#Override
public void onConnected(Bundle bundle) {
// Once connected with google api, get the location
//displayLocation();
if (mRequestingLocationUpdates) {
startLocationUpdates();
}
if (mLastLocation == null) {
mLastLocation =
LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
mLastUpdateTime = DateFormat.getTimeInstance().format(new Date());
displayLocation();
}
}
#Override
public void onConnectionSuspended(int i) {
mGoogleApiClient.connect();
}
#Override
public void onLocationChanged(Location location) {
// Assign the new location
mLastLocation = location;
mLastUpdateTime = DateFormat.getTimeInstance().format(new Date());
Log.d("Location changed ", "Location changed " + mLastLocation);
Log.d("Last updated time", "Last updated location " + mLastUpdateTime);
// Displaying the new location on UI
displayLocation();
}
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Log.i(TAG, "Connection failed: ConnectionResult.getErrorCode() = "
+ connectionResult.getErrorCode());
}
public void postDatabaseDetails() {
session = new SessionManager(getApplicationContext());
HashMap<String, String> user = session.getUserDetails();
sessionMobileNo = user.get(SessionManager.KEY_MOBILENO);
sessionDriverName = user.get(SessionManager.KEY_NAME);
sessionDID = user.get(SessionManager.KEY_DEVICEID);
sessiondriverUserId = user.get(SessionManager.KEY_DRIVERUSERID);
Calendar in = Calendar.getInstance();
Date dt = new Date();
in.setTimeZone(TimeZone.getTimeZone("Asia/Kolkata"));
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat stf = new SimpleDateFormat("HH:mm:ss");
date = sdf.format(dt);
time = stf.format(dt);
String timeval = time.toString();
String dateval = date.toString();
dateTime = date.toString() + " " + time.toString();
doc = sessionDID + "" + dateTime;
isInternet = cd.isConnectingToInternet();
if (isInternet) {
long id = driverDbHelper.insertDriverDetails(doc, sessionDID,
sessiondriverUserId, latitude, longitude,
fullAreaName, dateTime, "DEMO", sync_no, dateval, timeval);
postDriverDetails();
Log.d("GPS", "Service started after 2 mins with internet");
} else {
Log.d("Internet status", "No internet available for service");
long id = driverDbHelper.insertDriverDetails(doc, sessionDID,
sessiondriverUserId, latitude, longitude,
"NA", dateTime, "DEMO", sync_no, dateval, timeval);
Log.d("GPS", "Service started after 2 mins without internet");
}
}
I am calling this service from the activity HomePage.java , but from where do i have to call the below code , from onStart() or after setContentView(R.layout.home)?
try {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.MINUTE, 2);
AlarmManager am = (AlarmManager)
getSystemService(Context.ALARM_SERVICE);
Intent i = new Intent(Home_Page.this, AlarmManagerService.class);
PendingIntent pi = PendingIntent.getService(Home_Page.this, 0, i,0);
am.setInexactRepeating(AlarmManager.RTC_WAKEUP,
calendar.getTimeInMillis(), EXEC_INTERVAL, pi);
} catch (Exception e) {
e.printStackTrace();
}
I hope i can get some suggestions to improve the above service code as i want it to run smoothly in background updating the latitude and longitude at regular intervals.

Use less battery with GPS location every 15 minutes

I'm implementing an application which need to send a location to GCM every 15 minutes. I implemented an AlarmManager which will be called every 15 minute.
Here is my class of my alarmmanager
public class LocationAlarmManager {
Context mContext = null;
public LocationAlarmManager (Context context) {
mContext = context;
}
private AlarmManager alarmManager;
private Intent gpsTrackerIntent;
private PendingIntent pendingIntent;
private static final String TAG = "LocationAlarmManager";
public void startAlarmManager() {
Log.d(TAG, "startAlarmManager");
alarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
gpsTrackerIntent = new Intent(mContext, GpsTrackerAlarmReceiver.class);
pendingIntent = PendingIntent.getBroadcast(mContext, 0, gpsTrackerIntent, 0);
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime(),
15 * 60000, // 60000 = 1 minute
pendingIntent);
}
public void cancelAlarmManager() {
Log.d(TAG, "cancelAlarmManager");
Intent gpsTrackerIntent = new Intent(mContext, GpsTrackerAlarmReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, gpsTrackerIntent, 0);
AlarmManager alarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
alarmManager.cancel(pendingIntent);
}
}
That one calls GpsTrackerAlarmReceiver
// make sure we use a WakefulBroadcastReceiver so that we acquire a partial wakelock
public class GpsTrackerAlarmReceiver extends WakefulBroadcastReceiver {
private static final String TAG = "GpsTrackerAlarmReceiver";
#Override
public void onReceive(Context context, Intent intent) { context.startService(new Intent(context, SmartLocationService.class));
}
}
For handling my location I implemented the following in my SmartLocationService.
public class SmartLocationService extends Service implements
GooglePlayServicesClient.ConnectionCallbacks,
GooglePlayServicesClient.OnConnectionFailedListener,
LocationListener {
private static final String TAG = "SmartLocationService";
// use the websmithing defaultUploadWebsite for testing and then check your
// location with your browser here: https://www.websmithing.com/gpstracker/displaymap.php
private String defaultUploadWebsite;
private boolean currentlyProcessingLocation = false;
private LocationRequest locationRequest;
private LocationClient locationClient;
public LocationManager locationManager;
Context context;
// flag for GPS status
public boolean isGPSEnabled = false;
// flag for network status
boolean isNetworkEnabled = false;
// flag for GPS status
boolean canGetLocation = false;
Location location; // location
double latitude; // latitude
double longitude; // longitude
public Location previousBestLocation;
private double mLastLatitudeLocation = 0;
private double mLastLongitudeLocation = 0;
#Override
public void onCreate() {
super.onCreate();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
// if we are currently trying to get a location and the alarm manager has called this again,
// no need to start processing a new location.
if (!currentlyProcessingLocation) {
currentlyProcessingLocation = true;
startTracking();
}
return START_NOT_STICKY;
}
private void startTracking() {
Log.d(TAG, "startTracking");
if (GooglePlayServicesUtil.isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS) {
locationClient = new LocationClient(this,this,this);
if (!locationClient.isConnected() || !locationClient.isConnecting()) {
locationClient.connect();
}
} else {
Log.e(TAG, "unable to connect to google play services.");
}
}
protected void sendLocationDataToWebsite(Location loc) {
MessageHandler messageHandler = new MessageHandler(SmartLocationService.this);
messageHandler.sendLocationMessage(loc); //send location to GCM
}
#Override
public void onDestroy() {
super.onDestroy();
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onLocationChanged(Location location) {
if (location != null) {
Log.e(TAG, "position: " + location.getLatitude() + ", " + location.getLongitude() + " accuracy: " + location.getAccuracy());
sendLocationDataToWebsite(location);
}
}
private void stopLocationUpdates() {
if (locationClient != null && locationClient.isConnected()) {
locationClient.removeLocationUpdates(this);
locationClient.disconnect();
}
}
/**
* Called by Location Services when the request to connect the
* client finishes successfully. At this point, you can
* request the current location or start periodic updates
*/
#Override
public void onConnected(Bundle bundle) {
context = getApplicationContext();
Log.d(TAG, "onConnected");
locationRequest = LocationRequest.create();
locationRequest.setInterval(900000); // milliseconds
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
locationClient.requestLocationUpdates(locationRequest, this);
}
/**
* Called by Location Services if the connection to the
* location client drops because of an error.
*/
#Override
public void onDisconnected() {
Log.e(TAG, "onDisconnected");
stopLocationUpdates();
stopSelf();
}
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Log.e(TAG, "onConnectionFailed");
stopLocationUpdates();
stopSelf();
}
}
This works, but my locationservice get called more then once in my timeslot of 15 minutes. Anyone know why? Is this a good way to use less battery power (GPS)?
Thanks

Starting a BroadcastReceiver from activity

I'm trying to start a BroadcastReceiver from an activity - and it seems the receiver doesnt start and I dont get any output in logCat for it.
Im trying to start the receiver by doing this:
Intent intent = new Intent();
intent.setAction(getPackageName()+".mybroadcast");
sendBroadcast(intent);
Also here is my receiver class:
public class TriggerReceiver extends BroadcastReceiver implements
GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener {
private Vibrator v;
private CountDownTimer timer;
private final long INTERVAL = 1000;
private final long TWO_MINUTES = INTERVAL * 60 * 2;
// === variables
private Context context;
private final String TAG = TriggerReceiver.class.getSimpleName();
#Override
public void onReceive(Context context, Intent intent) {
this.context = context;
Log.e(TAG, "Trigger receiver starting");
v = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
mRequestingLocationUpdates = false;
buildGoogleApiClient();
mGoogleApiClient.connect();
}
//
private void startTimer(final Location location) {
String message = context.getString(R.string.emergency_text).replace("/location here/", getMapLink(location));
sendSMS(message);
showNotification();
Log.e(TAG, " sending sms and this is the location " + location.getLatitude() + " " + location.getLongitude());
timer = new CountDownTimer(TWO_MINUTES, INTERVAL) {
#Override
public void onTick(long millisUntilFinished) {
Log.e(TAG, "Tick " + (millisUntilFinished / 1000));
int elapsedTime = (int) ((millisUntilFinished / 1000) % 60);
int minutes = (int) ((millisUntilFinished / 1000) / 60);
Log.e(TAG, elapsedTime + " | " + minutes);
if (minutes == 2) {
minutes = 1;
elapsedTime = 60;
}
}
//TODO start activity and pass extras to intent
#Override
public void onFinish() {
startTimer(mCurrentLocation);
}
}.start();
}
private void sendSMS(String message) {
ArrayList<Contact> contacts = ((BaseActivity) context).getContacts();
for (Contact contact : contacts) {
sendSMSmsg(contact.getNumber(), message);
}
}
private void stopTimer() {
timer.cancel();
}
/*location*/
/**
* Provides the entry point to Google Play services.
*/
protected GoogleApiClient mGoogleApiClient;
/**
* Stores parameters for requests to the FusedLocationProviderApi.
*/
protected LocationRequest mLocationRequest, quickLocationRequest;
/**
* Represents a geographical location.
*/
protected Location mCurrentLocation, mLastLocation;
protected Boolean mRequestingLocationUpdates;
/**
* Builds a GoogleApiClient. Uses the {#code #addApi} method to request the
* LocationServices API.
*/
protected synchronized void buildGoogleApiClient() {
Log.i(TAG, "Building GoogleApiClient");
mGoogleApiClient = new GoogleApiClient.Builder(context)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
createLocationRequest();
}
protected void createLocationRequest() {
LocationRequest mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(110000);
mLocationRequest.setFastestInterval(55000);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
}
protected void startLocationUpdates() {
LocationServices.FusedLocationApi.requestLocationUpdates(
mGoogleApiClient, mLocationRequest, this);
}
protected void doQuickLocationUpdate() {
quickLocationRequest = new LocationRequest();
quickLocationRequest.setInterval(1000);
quickLocationRequest.setFastestInterval(500);
quickLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
// The final argument to {#code requestLocationUpdates()} is a LocationListener
// (http://developer.android.com/reference/com/google/android/gms/location/LocationListener.html).
LocationServices.FusedLocationApi.requestLocationUpdates(
mGoogleApiClient, quickLocationRequest, this);
}
protected void stopLocationUpdates() {
//TODO add this in onbackpresses and onStop
LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
}
#Override
public void onConnected(Bundle connectionHint) {
Log.i(TAG, "Connected to GoogleApiClient");
mLastLocation = LocationServices.FusedLocationApi.getLastLocation(
mGoogleApiClient);
if (mLastLocation != null) {
startTimer(mLastLocation);
} else {
doQuickLocationUpdate();
}
if (mCurrentLocation == null) {
mCurrentLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
Log.d(TAG, "onConnected mCurrentLocation = " + mCurrentLocation.getLatitude());
}
if (mRequestingLocationUpdates) {
startLocationUpdates();
}
}
#Override
public void onLocationChanged(Location location) {
mCurrentLocation = location;
if (quickLocationRequest != null) {
quickLocationRequest = null;
}
if (timer == null) {
startTimer(location);
}
Log.d(TAG, "onLocationChanged mCurrentLocation = " + mCurrentLocation.getLatitude());
}
#Override
public void onConnectionSuspended(int cause) {
// The connection to Google Play services was lost for some reason. We call connect() to
// attempt to re-establish the connection.
Log.i(TAG, "Connection suspended");
mGoogleApiClient.connect();
}
#Override
public void onConnectionFailed(ConnectionResult result) {
// Refer to the javadoc for ConnectionResult to see what error codes might be returned in
// onConnectionFailed.
Log.i(TAG, "Connection failed: ConnectionResult.getErrorCode() = " + result.getErrorCode());
}
public void sendSMSmsg(String phoneNumber, String message) {
v.vibrate(50);
// ArrayList<PendingIntent> sentPendingIntents = new ArrayList<PendingIntent>();
// ArrayList<PendingIntent> deliveredPendingIntents = new ArrayList<PendingIntent>();
// PendingIntent sentPI = PendingIntent.getBroadcast(context, 0,
// new Intent(context, SmsSentReceiver.class), 0);
// PendingIntent deliveredPI = PendingIntent.getBroadcast(context, 0,
// new Intent(context, SmsDeliveredReceiver.class), 0);
// try {
// SmsManager sms = SmsManager.getDefault();
// ArrayList<String> mSMSMessage = sms.divideMessage(message);
// for (int i = 0; i < mSMSMessage.size(); i++) {
// sentPendingIntents.add(i, sentPI);
// deliveredPendingIntents.add(i, deliveredPI);
// }
// sms.sendMultipartTextMessage(phoneNumber, null, mSMSMessage,
// sentPendingIntents, deliveredPendingIntents);
//
// } catch (Exception e) {
// e.printStackTrace();
// }
}
public void showNotification() {
NotificationCompat.Builder mBuilder;
if (Build.VERSION.SDK_INT >= 16) {
mBuilder = new NotificationCompat.Builder(context)
.setSmallIcon(R.mipmap.sosicon)
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
.setContentTitle(context.getString(R.string.app_name))
.setAutoCancel(true)
.setContentText(context.getString(R.string.notification_text));
// Creates an explicit intent for an Activity in your app
Intent resultIntent = new Intent();
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
stackBuilder.addNextIntent(resultIntent);
PendingIntent resultPendingIntent =
stackBuilder.getPendingIntent(
0,
PendingIntent.FLAG_UPDATE_CURRENT
);
mBuilder.setContentIntent(resultPendingIntent);
NotificationManager mNotificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.notify(10142, mBuilder.build());
}
}
public String getMapLink(Location location) {
if (location != null) {
Log.d(TAG, "http://maps.google.com/maps?z=12&t=m&q=loc:" + location.getLatitude() + "+" + location.getLongitude());
return "http://maps.google.com/maps?z=12&t=m&q=loc:" + location.getLatitude() + "+" + location.getLongitude();
} else {
return null;
}
}
//
}
and also the declaration in the manifest:
<receiver
android:name=".TriggerReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="PACKAGENAME.mybroadcast" />
</intent-filter>
</receiver>
So what can be wrong here and why is the receiver not starting?
Thanks

Categories

Resources