-
Notifications
You must be signed in to change notification settings - Fork 48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Check for Location Permission #12
Conversation
e634e6b
to
092448c
Compare
* </p> | ||
*/ | ||
public final long getElapsedTimeMillis() { | ||
return (System.nanoTime() - startTimeNanos) / 1000000; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT Magic number
@@ -184,7 +188,7 @@ private void sendEvents(List<Event> events, Callback httpCallback) { | |||
} | |||
|
|||
private boolean startTelemetry() { | |||
if (!isTelemetryEnabled) { | |||
if (!isTelemetryEnabled && checkLocationPermission()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding the check here would mean that
Lines 190 to 192 in a2ec694
schedulerFlusher.register(); | |
Clock clock = obtainClock(); | |
schedulerFlusher.schedule(clock.giveMeTheElapsedRealtime()); |
The expected behavior is to have everything working except the location-related stuff.
We added the following // TODO
comments to analyze if it would make sense to add the check within TelemetryService
or not:
Line 39 in a2ec694
// TODO Check for location permissions here |
Line 51 in a2ec694
// TODO If the location receiver was not created (permissions were not granted) do not unregister |
The idea of not creating the service if the permissions are not granted is great but we should keep the rest of functionalities working.
Now it's a good moment to extract that block of code into a well-named method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not exploring the idea commented in #12 (comment)?
The idea of not creating the service if the permissions are not granted is great but we should keep the rest of functionalities working.
It would be also more performant because we wouldn't create the service at all if the permissions are not granted (fewer resources, less memory, 💪 battery). What do you think?
In any case, we should take care of any potential leaks 👇
} | ||
|
||
private void permissionBackoff(final Context context) { | ||
final Handler handler = new Handler(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code may cause a memory leak.
👀 How to Leak a Context: Handlers & Inner Classes
* In this version, the interval creation is deterministic and has no concept of | ||
* maxElapsedTimeMillis, it's up to the application to stop. It also adjusts its default values. | ||
*/ | ||
public class ExponentialBackoff { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class is only used internally, so no need to be public
(we don't need to expose it publicly). The same thing happens with the properties and the methods included.
registerEventReceiver(); | ||
isOpted = true; | ||
if (checkLocationPermission()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding the check here would mean that
Lines 203 to 204 in 22f1465
registerEventReceiver(); | |
isOpted = true; |
No need to register the receiver in charge of dealing with the location probes if permissions are not granted. Besides that, isOpted
should only be true
if previous actions startLocation()
and registerEventReceiver()
were executed, meaning that location is opted. If not, we may have issues when optLocationOut()
.
handler.postDelayed(permissionCheckRunnable, nextWaitTime); | ||
} | ||
|
||
private final class PermissionCheckRunnable implements Runnable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is PermissionCheckRunnable
a nested class of MapboxTelemetry
? Is it necessary? Could we extract PermissionCheckRunnable
class into a PermissionCheckRunnable.java
file/class?
@@ -48,8 +47,10 @@ public int onStartCommand(Intent intent, int flags, int startId) { | |||
|
|||
@Override | |||
public void onDestroy() { | |||
// TODO If the location receiver was not created (permissions were not granted) do not unregister | |||
unregisterLocationReceiver(); | |||
if (locationReceiver != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is not necessary because if we never create TelemetryService
we can't get a null
locationReceiver
. This is because how we're dealing with the TelemetryService
creation in MapboxTelemetry
. Am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good so far.
Some minor details.
We should also include some unit tests around the new location permission check.
class ExponentialBackoff { | ||
|
||
/** The default initial interval value in milliseconds (10 seconds). */ | ||
public static final int DEFAULT_INITIAL_INTERVAL_MILLIS = 10_000; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Access can be private
The IDE hints you about the visibility, take advantage of it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same thing happens with the properties and the methods 👇
|
||
void locationAndReceiverSetup() { | ||
registerEventReceiver(); | ||
isOpted = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Besides that,
isOpted
should only betrue
if previous actionsstartLocation()
andregisterEventReceiver()
were executed, meaning that location is opted.
startLocation()
and registerEventReceiver()
might fail, order matters.
@Override | ||
public void run() { | ||
if (PermissionsManager.areLocationPermissionsGranted(context)) { | ||
mapboxTelemetry.locationAndReceiverSetup(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If isTelemetryEnabled
or isOpted
change their value between intervals will cause problems.
No need to create locationAndReceiverSetup
method because we could use the public
method optIn
which will check those variables too.
} | ||
|
||
private void permissionBackoff() { | ||
Handler handler = new Handler(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no need to create a handler and a counter here because we're already doing it in PermissionCheckRunnable
(it may be interesting to pass them in through the constructor though).
|
||
PermissionCheckRunnable permissionCheckRunnable = new PermissionCheckRunnable(context, this); | ||
|
||
long nextWaitTime = counter.nextBackOffMillis(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to calculate nextWaitTime
and calling handler.postDelayed(permissionCheckRunnable, nextWaitTime)
because of ☝️
What about calling permissionCheckRunnable.run()
instead?
*/ | ||
private long startTimeNanos; | ||
|
||
public ExponentialBackoff() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Access can be package-private
} | ||
|
||
/** Sets the interval back to the initial retry interval and restarts the timer. */ | ||
public final void reset() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Access can be package-private
/** | ||
* Get the next back off interval | ||
*/ | ||
public long nextBackOffMillis() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Access can be package-private
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#12 (comment)
And the rest of the methods 👇
@Override | ||
public void run() { | ||
if (PermissionsManager.areLocationPermissionsGranted(context)) { | ||
mapboxTelemetry.optIn(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this won't work properly because mapboxTelemetry.optIn()
👉 optLocationIn()
👉 checkLocationPermission()
👉 permissionBackoff()
which will create a new instance of PermissionCheckRunnable
so the intervals will be reset (loosing the exponential backoff behavior).
What about creating a lazy initialization method for PermissionCheckRunnable
creation?
@@ -473,4 +473,37 @@ public void checksUnregisterReceiverWhenOptedOut() throws Exception { | |||
|
|||
verify(mockedLocalBroadcastManager, times(1)).unregisterReceiver(eq(theEventReceiver)); | |||
} | |||
|
|||
@Test | |||
public void checksLocationPermissionSet() throws Exception { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the same exact test as the checksOptedIn
one. We should remove it.
} | ||
|
||
@Test | ||
public void checksLocationPermissionNotSet() throws Exception { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we're not testing the location permissions.
It actually returns false
because telemetry is not enabled and isTelemetryEnabled
is false
Line 200 in 04732b8
if (isTelemetryEnabled && !isOpted && checkLocationPermission()) { |
We should revisit this test.
import com.mapbox.services.android.core.permissions.PermissionsManager; | ||
|
||
class PermissionCheckRunnable implements Runnable { | ||
private static PermissionCheckRunnable permissionCheckRunnable; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Private field 'permissionCheckRunnable' is never used
|
||
private void permissionBackoff() { | ||
PermissionCheckRunnable permissionCheckRunnable = obtainPermissionCheckRunnable(); | ||
permissionCheckRunnable.run(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that if we call permissionCheckRunnable.run()
here the intervals will be reset (losing the exponential backoff behavior), not completely sure though we should test it.
Related 👉 https://github.com/mapbox/mapbox-events-android/pull/12/files#r155230708
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested and works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested and works.
Still wondering why it works as expected 🤔
Anyway, we should revisit this later because I was thinking about firing “permissionBackoff” https://github.com/mapbox/mapbox-events-android/pull/12/files#diff-d0541eaee9bf537cfb1016de23583b33R295 and then not worry about it anymore from MapboxTelemetry
because PermissionCheckRunnable
could run by its own managing the intervals internally through ExponentialBackoff
and notify MapboxTelemetry
when the permissions are granted and at that point stop/reset everything so we don’t leak anything and leaving MapboxTelemetry
ready just in case the user remove the permissions and “permissionBackoff” would need to be fired again.
This is the scenario that we need to take into account, the happy path and also the unhappy path which in our case is what happens when the user remove the permissions? Does the whole machinery still work/behave as expected?
Apart from that, we should also test thoroughly that we’re not leaking any object.
…ore artifact out of libcore and liblocation modules
* Add AudioType Support * NavigationUtils + Refactor
* add missing event field to nav events and gson builder with nav events serializers * add missing telemetry client events tests and expose missing navigation DTOs setters
- make priority editable
- adjust placement of @LocationEnginePriority.PowerMode annotations
- add in exponential backoff - permission check - integration into system
a41bc97
to
79148a3
Compare
Before requesting location updates through the location engine, check to see if the application already has location permission. If not, prevent location updates from running and fall back on an exponential backoff system for checking on the status of the location permission. Once permission checks pass, allow location engine to operate normally.