-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathLocationFetcher
779 lines (696 loc) · 32.3 KB
/
LocationFetcher
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
package com.traverlersnews.util;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.google.android.gms.common.api.*;
import com.google.android.gms.location.*;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
/**
* A helper class that monitors the available location info on behalf of a requesting activity or application.
*/
public class LocationFetcher extends LocationCallback {
/**
* Delivers relevant events required to obtain (valid) location info.
*/
public interface Listener {
/**
* Called when the user needs to grant the app location permission at run time.
* This is only necessary on newer Android systems (API level >= 23).
* If you want to show some explanation up front, do that, then call {@link #requestLocationPermission()}.
* Alternatively, you can call {@link #requestAndPossiblyExplainLocationPermission()}, which will request the
* location permission right away and invoke {@link #onExplainLocationPermission()} only if the user declines.
* Both methods will bring up the system permission dialog.
*/
void onNeedLocationPermission();
/**
* Called when the user has declined the location permission and might need a better explanation as to why
* your app really depends on it.
* You can show some sort of dialog or info window here and then - if the user is willing - ask again for
* permission with {@link #requestLocationPermission()}.
*/
void onExplainLocationPermission();
/**
* Called when the user has declined the location permission at least twice or has declined once and checked
* "Don't ask again" (which will cause the system to permanently decline it).
* You can show some sort of message that explains that the user will need to go to the app settings
* to enable the permission. You may use the preconfigured OnClickListeners to send the user to the app
* settings page.
*
* @param fromView OnClickListener to use with a view (e.g. a button), jumps to the app settings
* @param fromDialog OnClickListener to use with a dialog, jumps to the app settings
*/
void onLocationPermissionPermanentlyDeclined(View.OnClickListener fromView, DialogInterface.OnClickListener fromDialog);
/**
* Called when a change of the location provider settings is necessary.
* You can optionally show some informative dialog and then request the settings change with
* {@link #changeLocationSettings()}.
*/
void onNeedLocationSettingsChange();
/**
* In certain cases where the user has switched off location providers, changing the location settings from
* within the app may not work. The LocationFetcher will attempt to detect these cases and offer a redirect to
* the system location settings, where the user may manually enable on location providers before returning to
* the app.
* You can prompt the user with an appropriate message (in a view or a dialog) and use one of the provided
* OnClickListeners to jump to the settings.
*
* @param fromView OnClickListener to use with a view (e.g. a button), jumps to the location settings
* @param fromDialog OnClickListener to use with a dialog, jumps to the location settings
*/
void onFallBackToSystemSettings(View.OnClickListener fromView, DialogInterface.OnClickListener fromDialog);
/**
* Called when a new and valid location is available.
* If you chose to reject mock locations, this method will only be called when a real location is available.
*
* @param location the current user location
*/
void onNewLocationAvailable(Location location);
/**
* Called when the presence of mock locations was detected and {@link #allowMockLocations} is {@code false}.
* You can use this callback to scold the user or do whatever. The user can usually disable mock locations by
* either switching off a running mock location app (on newer Android systems) or by disabling mock location
* apps altogether. The latter can be done in the phone's development settings. You may show an appropriate
* message and then use one of the provided OnClickListeners to jump to those settings.
*
* @param fromView OnClickListener to use with a view (e.g. a button), jumps to the development settings
* @param fromDialog OnClickListener to use with a dialog, jumps to the development settings
*/
void onMockLocationsDetected(View.OnClickListener fromView, DialogInterface.OnClickListener fromDialog);
/**
* Called when an error has occurred.
*
* @param type the type of error that occurred
* @param message a plain-text message with optional details
*/
void onError(ErrorType type, String message);
}
/**
* Possible values for the desired location accuracy.
*/
public enum Accuracy {
/**
* Highest possible accuracy, typically within 30m
*/
HIGH,
/**
* Medium accuracy, typically within a city block / roughly 100m
*/
MEDIUM,
/**
* City-level accuracy, typically within 10km
*/
LOW,
/**
* Variable accuracy, purely dependent on updates requested by other apps
*/
PASSIVE
}
public enum ErrorType {
/**
* An error with the user's location settings
*/
SETTINGS,
/**
* An error with the retrieval of location info
*/
RETRIEVAL
}
private final String TAG = getClass().getSimpleName();
private final int REQUEST_CHECK_SETTINGS = 0;
private final int REQUEST_LOCATION_PERMISSION = 1;
// Parameters
protected Context context;
private Activity activity;
private Listener listener;
private int priority;
private long updateInterval;
private long meter;
private boolean allowMockLocations;
private boolean logs;
private boolean quiet;
// Internal state
public boolean permissionGranted;
private boolean locationRequested;
private boolean locationStatusOk;
private boolean changeSettings;
private boolean updatesRequested;
private Location bestLocation;
private LocationRequest locationRequest;
private boolean mockLocationsEnabled;
private int numTimesPermissionDeclined;
private FusedLocationProviderClient fusedLocationProviderClient;
// Mock location rejection
private Location lastMockLocation;
private int numGoodReadings;
/**
* Constructs a LocationFetcher instance that will listen for valid location updates.
*
* @param context the context of the application or activity that wants to receive location updates
* @param listener a listener that will receive location-related events
* @param accuracy the desired accuracy of the loation updates
* @param updateInterval the interval (in milliseconds) at which the activity can process updates
* @param allowMockLocations whether or not mock locations are acceptable
*/
public LocationFetcher(final Context context, Listener listener, Accuracy accuracy, long meter, long updateInterval,
boolean allowMockLocations) {
this.context = context;
if (context instanceof Activity)
this.activity = (Activity) context;
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context);
this.listener = listener;
switch (accuracy) {
case HIGH:
priority = LocationRequest.PRIORITY_HIGH_ACCURACY;
break;
case MEDIUM:
priority = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY;
break;
case LOW:
priority = LocationRequest.PRIORITY_LOW_POWER;
break;
case PASSIVE:
default:
priority = LocationRequest.PRIORITY_NO_POWER;
}
this.updateInterval = updateInterval;
this.meter = meter;
this.allowMockLocations = allowMockLocations;
}
/**
* Makes the LocationFetcher print info log messages.
*
* @param logs whether or not the LocationFetcher should print logs log messages.
*/
public void setLogEnable(boolean logs) {
this.logs = logs;
}
/**
* Mutes/unmutes all log output.
* You may want to mute the LocationFetcher in production.
*
* @param quiet whether or not to disable all log output (including errors).
*/
public void setQuiet(boolean quiet) {
this.quiet = quiet;
}
/**
* Starts the LocationFetcher and makes it subscribe to valid location updates.
* Call this method when your application or activity becomes awake.
*/
public void start() {
checkMockLocations();
acquireLocation();
}
/**
* Updates the active Activity for which the LocationFetcher manages location updates.
* When you want the LocationFetcher to start and stop with your overall application, but service different
* activities, call this method at the end of your {@link Activity#onResume()} implementation.
*
* @param activity the activity that wants to receive location updates
* @param listener a listener that will receive location-related events
*/
public void register(Activity activity, Listener listener) {
this.activity = activity;
this.listener = listener;
checkInitialLocation();
acquireLocation();
}
/**
* Stops the LocationFetcher and makes it unsubscribe from any location updates.
* Call this method right before your application or activity goes to sleep.
*/
public void stop() {
LocationServices.getFusedLocationProviderClient(context).removeLocationUpdates(this);
permissionGranted = false;
locationRequested = false;
locationStatusOk = false;
updatesRequested = false;
}
/**
* Clears the active Activity and its listener.
* Until you register a new activity and listener, the LocationFetcher will silently produce error messages.
* When you want the LocationFetcher to start and stop with your overall application, but service different
* activities, call this method at the beginning of your {@link Activity#onPause()} implementation.
*/
public void unregister() {
this.activity = null;
this.listener = null;
}
/**
* In rare cases (e.g. after losing connectivity) you may want to reset the LocationFetcher and have it start
* from scratch. Use this method to do so.
*/
public void reset() {
permissionGranted = false;
locationRequested = false;
locationStatusOk = false;
updatesRequested = false;
acquireLocation();
}
private void getLastLocation() {
if (ActivityCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(activity, 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;
}
if (!updatesRequested) {
fusedLocationProviderClient.requestLocationUpdates(locationRequest, this, Looper.myLooper());
updatesRequested = true;
}
fusedLocationProviderClient.getLastLocation().addOnSuccessListener(location -> {
if(location!=null) {
bestLocation = location;
}
onLocationChanged(bestLocation);
Log.i(TAG, "getLastLocation"+bestLocation);
});
}
/**
* Returns the best valid location currently available.
* Usually, this will be the last valid location that was received.
*
* @return the best valid location
*/
public Location getBestLocation() {
return bestLocation;
}
/**
* The first time you call this method, it brings up a system dialog asking the user to give location permission to
* the app. On subsequent calls, if the user has previously declined permission, this method invokes
* {@link Listener#onExplainLocationPermission()}.
*/
public void requestAndPossiblyExplainLocationPermission() {
if (permissionGranted) return;
if (activity == null) {
if (!quiet)
Log.e(TAG, "Need location permission, but no activity is registered! " +
"Specify a valid activity when constructing " + TAG +
" or register it explicitly with register().");
return;
}
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.ACCESS_FINE_LOCATION)
&& listener != null)
listener.onExplainLocationPermission();
else
requestLocationPermission();
}
/**
* Brings up a system dialog asking the user to give location permission to the app.
*/
public void requestLocationPermission() {
if (activity == null) {
if (!quiet)
Log.e(TAG, "Need location permission, but no activity is registered! " +
"Specify a valid activity when constructing " + TAG +
" or register it explicitly with register().");
return;
}
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_LOCATION_PERMISSION);
}
/**
* Call this method at the end of your {@link Activity#onRequestPermissionsResult} implementation to notify the
* LocationFetcher of an update in permissions.
*
* @param requestCode the request code returned to the activity (simply pass it on)
* @param grantResults the results array returned to the activity (simply pass it on)
* @return {@code true} if the location permission was granted, {@code false} otherwise
*/
public boolean onPermissionsUpdated(int requestCode, int[] grantResults) {
if (requestCode != REQUEST_LOCATION_PERMISSION) return false;
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
acquireLocation();
return true;
} else {
numTimesPermissionDeclined++;
if (!quiet)
Log.i(TAG, "Location permission request denied.");
if (numTimesPermissionDeclined >= 2 && listener != null)
listener.onLocationPermissionPermanentlyDeclined(onGoToAppSettingsFromView,
onGoToAppSettingsFromDialog);
return false;
}
}
/**
* Call this method at the end of your {@link Activity#onActivityResult} implementation to notify the
* LocationFetcher of a change in location provider settings.
*
* @param requestCode the request code returned to the activity (simply pass it on)
* @param resultCode the result code returned to the activity (simply pass it on)
*/
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (requestCode != REQUEST_CHECK_SETTINGS) return;
final LocationSettingsStates states = LocationSettingsStates.fromIntent(intent);
if (resultCode == Activity.RESULT_OK) {
changeSettings = false;
locationStatusOk = true;
}
acquireLocation();
}
/**
* Brings up an in-app system dialog that requests a change in location provider settings.
* The settings change may involve switching on GPS and/or network providers and depends on the accuracy and
* update interval that was requested when constructing the LocationFetcher.
* Call this method only from within {@link Listener#onNeedLocationSettingsChange()}.
*/
public void changeLocationSettings() {
requestLocation();
}
private void acquireLocation() {
if (!permissionGranted) checkLocationPermission();
if (!permissionGranted) {
if (numTimesPermissionDeclined >= 2) return;
if (listener != null)
listener.onNeedLocationPermission();
else if (!quiet)
Log.e(TAG, "Need location permission, but no listener is registered! " +
"Specify a valid listener when constructing " + TAG +
" or register it explicitly with register().");
return;
}
if (!locationRequested) {
requestLocation();
return;
}
if (!locationStatusOk) {
if (changeSettings) {
if (listener != null)
listener.onNeedLocationSettingsChange();
else if (!quiet)
Log.e(TAG, "Need location settings change, but no listener is " +
"registered! Specify a valid listener when constructing " + TAG +
" or register it explicitly with register().");
} else
checkProviders();
return;
}
if (!updatesRequested) {
requestLocationUpdates();
// Check back in a few
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
acquireLocation();
}
}, 10000);
return;
}
if (!checkLocationAvailability()) {
// Something is wrong - probably the providers are disabled.
checkProviders();
}
}
private void checkInitialLocation() {
if (!permissionGranted || !locationRequested || !locationStatusOk)
return;
try {
getLastLocation();
} catch (SecurityException e) {
if (!quiet)
Log.e(TAG, "Error while requesting last location:\n " +
e.toString());
if (listener != null)
listener.onError(ErrorType.RETRIEVAL, "Could not retrieve initial location:\n" +
e.getMessage());
}
}
private void checkMockLocations() {
// Starting with API level >= 18 we can (partially) rely on .isFromMockProvider()
// (http://developer.android.com/reference/android/location/Location.html#isFromMockProvider%28%29)
// For API level < 18 we have to check the Settings.Secure flag
mockLocationsEnabled = false;
}
public void checkLocationPermission() {
permissionGranted = Build.VERSION.SDK_INT < 23 ||
ContextCompat.checkSelfPermission(context,
Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
}
private void requestLocation() {
if (!permissionGranted) return;
locationRequest = LocationRequest.create();
locationRequest.setPriority(priority);
if (updateInterval != 0)
locationRequest.setInterval(updateInterval);
if (meter != 0)
locationRequest.setSmallestDisplacement(meter);
// Create LocationSettingsRequest object using location request
LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
builder.addLocationRequest(locationRequest);
builder.setAlwaysShow(true);
LocationSettingsRequest locationSettingsRequest = builder.build();
// Check whether location settings are satisfied
// https://developers.google.com/android/reference/com/google/android/gms/location/SettingsClient
SettingsClient settingsClient = LocationServices.getSettingsClient(context);
Task<LocationSettingsResponse> task = settingsClient.checkLocationSettings(locationSettingsRequest);
task.addOnCompleteListener(task1 -> {
locationRequested = true;
try {
LocationSettingsResponse response = task1.getResult(ApiException.class);
// All location settings are satisfied. The client can initialize location
// requests here.
Log.v(TAG, "Location settings satisfied");
locationStatusOk = true;
checkInitialLocation();
} catch (ApiException exception) {
locationRequested = false;
Log.v(" Failed ", String.valueOf(exception.getStatusCode()));
switch (exception.getStatusCode()) {
case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
// Location settings are not satisfied. But could be fixed by showing the
// user a dialog.
// Cast to a resolvable exception.
ResolvableApiException resolvable = (ResolvableApiException) exception;
// Show the dialog by calling startResolutionForResult(),
// and check the result in onActivityResult().
try {
resolvable.startResolutionForResult(
activity,
REQUEST_CHECK_SETTINGS);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
break;
case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
// Location settings are not satisfied. However, we have no way to fix the
// settings so we won't show the dialog.
locationStatusOk = false;
changeSettings = true;
break;
}
}
});
}
private synchronized boolean checkLocationAvailability() {
if (!permissionGranted) return false;
final LocationAvailability[] la = new LocationAvailability[1];
try {
fusedLocationProviderClient.getLocationAvailability().addOnSuccessListener(new OnSuccessListener<LocationAvailability>() {
@Override
public void onSuccess(LocationAvailability locationAvailability) {
la[0] = locationAvailability;
}
});
return (la[0] != null && la[0].isLocationAvailable());
} catch (SecurityException e) {
if (!quiet)
Log.e(TAG, "Error while checking location availability:\n " + e.toString());
if (listener != null)
listener.onError(ErrorType.RETRIEVAL, "Could not check location availability:\n" +
e.getMessage());
return false;
}
}
private void checkProviders() {
// Do it the old fashioned way
LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
boolean gps = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
boolean network = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
if (gps || network) return;
if (listener != null)
listener.onFallBackToSystemSettings(onGoToLocationSettingsFromView, onGoToLocationSettingsFromDialog);
else if (!quiet)
Log.e(TAG, "Location providers need to be enabled, but no listener is " +
"registered! Specify a valid listener when constructing " + TAG +
" or register it explicitly with register().");
}
private void requestLocationUpdates() {
if (!permissionGranted || !locationRequested) return;
try {
getLastLocation();
} catch (SecurityException e) {
if (!quiet)
Log.e(TAG, "Error while requesting location updates:\n " +
e.toString());
if (listener != null)
listener.onError(ErrorType.RETRIEVAL, "Could not request location updates:\n" +
e.getMessage());
}
}
private DialogInterface.OnClickListener onGoToLocationSettingsFromDialog = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (activity != null) {
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
activity.startActivity(intent);
} else if (!quiet)
Log.e(TAG, "Need to launch an intent, but no activity is registered! " +
"Specify a valid activity when constructing " + TAG +
" or register it explicitly with register().");
}
};
private View.OnClickListener onGoToLocationSettingsFromView = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (activity != null) {
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
activity.startActivity(intent);
} else if (!quiet)
Log.e(TAG, "Need to launch an intent, but no activity is registered! " +
"Specify a valid activity when constructing " + TAG +
" or register it explicitly with register().");
}
};
private DialogInterface.OnClickListener onGoToDevSettingsFromDialog = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (activity != null) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS);
activity.startActivity(intent);
} else if (!quiet)
Log.e(TAG, "Need to launch an intent, but no activity is registered! " +
"Specify a valid activity when constructing " + TAG +
" or register it explicitly with register().");
}
};
private View.OnClickListener onGoToDevSettingsFromView = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (activity != null) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS);
activity.startActivity(intent);
} else if (!quiet)
Log.e(TAG, "Need to launch an intent, but no activity is registered! " +
"Specify a valid activity when constructing " + TAG +
" or register it explicitly with register().");
}
};
private DialogInterface.OnClickListener onGoToAppSettingsFromDialog = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (activity != null) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
intent.setData(uri);
activity.startActivity(intent);
} else if (!quiet)
Log.e(TAG, "Need to launch an intent, but no activity is registered! " +
"Specify a valid activity when constructing " + TAG +
" or register it explicitly with register().");
}
};
private View.OnClickListener onGoToAppSettingsFromView = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (activity != null) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
intent.setData(uri);
activity.startActivity(intent);
} else if (!quiet)
Log.e(TAG, "Need to launch an intent, but no activity is registered! " +
"Specify a valid activity when constructing " + TAG +
" or register it explicitly with register().");
}
};
private boolean isLocationPlausible(Location location) {
if (location == null) return false;
boolean isMock = mockLocationsEnabled || location.isFromMockProvider();
if (isMock) {
lastMockLocation = location;
numGoodReadings = 0;
} else
numGoodReadings = Math.min(numGoodReadings + 1, 1000000); // Prevent overflow
// We only clear that incident record after a significant show of good behavior
if (numGoodReadings >= 20) lastMockLocation = null;
// If there's nothing to compare against, we have to trust it
if (lastMockLocation == null) return true;
// And finally, if it's more than 1km away from the last known mock, we'll trust it
double d = location.distanceTo(lastMockLocation);
return (d > 1000);
}
@Override
public void onLocationResult(LocationResult locationResult) {
Location location = locationResult.getLocations().get(0);
boolean plausible = isLocationPlausible(location);
if (logs && !quiet)
Log.i(TAG, location.toString() +
(plausible ? " -> plausible" : " -> not plausible"));
if (!allowMockLocations && !plausible) {
if (listener != null) listener.onMockLocationsDetected(onGoToDevSettingsFromView,
onGoToDevSettingsFromDialog);
return;
}
bestLocation = location;
if (listener != null)
listener.onNewLocationAvailable(location);
else if (!quiet)
Log.w(TAG, "New location is available, but no listener is registered!\n" +
"Specify a valid listener when constructing " + TAG +
" or register it explicitly with register().");
super.onLocationResult(locationResult);
}
private void onLocationChanged(Location location) {
if (location == null) return;
boolean plausible = isLocationPlausible(location);
if (logs && !quiet)
Log.i(TAG, location.toString() +
(plausible ? " -> plausible" : " -> not plausible"));
if (!allowMockLocations && !plausible) {
if (listener != null) listener.onMockLocationsDetected(onGoToDevSettingsFromView,
onGoToDevSettingsFromDialog);
return;
}
bestLocation = location;
if (listener != null)
listener.onNewLocationAvailable(location);
else if (!quiet)
Log.w(TAG, "New location is available, but no listener is registered!\n" +
"Specify a valid listener when constructing " + TAG +
" or register it explicitly with register().");
}
@Override
public void onLocationAvailability(LocationAvailability locationAvailability) {
super.onLocationAvailability(locationAvailability);
if(!locationAvailability.isLocationAvailable() && listener!=null){
listener.onNeedLocationSettingsChange();
}else{
getLastLocation();
}
}
}