Skip to content
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

Merged
merged 15 commits into from
Dec 13, 2017
Merged

Check for Location Permission #12

merged 15 commits into from
Dec 13, 2017

Conversation

electrostat
Copy link
Contributor

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.

* </p>
*/
public final long getElapsedTimeMillis() {
return (System.nanoTime() - startTimeNanos) / 1000000;
Copy link
Contributor

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()) {
Copy link
Contributor

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

schedulerFlusher.register();
Clock clock = obtainClock();
schedulerFlusher.schedule(clock.giveMeTheElapsedRealtime());
won't be executed unless the user has the permissions granted and we don't want that.

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:


// 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.

Copy link
Contributor

@Guardiola31337 Guardiola31337 left a 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();
Copy link
Contributor

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 {
Copy link
Contributor

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()) {
Copy link
Contributor

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

will be executed and we don't want that.

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 {
Copy link
Contributor

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) {
Copy link
Contributor

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?

Copy link
Contributor

@Guardiola31337 Guardiola31337 left a 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;
Copy link
Contributor

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.

Copy link
Contributor

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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#12 (comment)

Besides that, isOpted should only be true if previous actions startLocation() and registerEventReceiver() were executed, meaning that location is opted.

startLocation() and registerEventReceiver() might fail, order matters.

@Override
public void run() {
if (PermissionsManager.areLocationPermissionsGranted(context)) {
mapboxTelemetry.locationAndReceiverSetup();
Copy link
Contributor

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();
Copy link
Contributor

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();
Copy link
Contributor

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() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#12 (comment)

Access can be package-private

}

/** Sets the interval back to the initial retry interval and restarts the timer. */
public final void reset() {
Copy link
Contributor

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() {
Copy link
Contributor

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

Copy link
Contributor

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();
Copy link
Contributor

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 {
Copy link
Contributor

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 {
Copy link
Contributor

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

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;
Copy link
Contributor

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();
Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested and works.

Copy link
Contributor

@Guardiola31337 Guardiola31337 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#12 (comment)

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.

Guardiola31337 and others added 7 commits December 13, 2017 13:51
* 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
@electrostat electrostat merged commit ab6e7ca into master Dec 13, 2017
@electrostat electrostat deleted the aa-permission-check branch December 13, 2017 20:07
@Guardiola31337 Guardiola31337 mentioned this pull request Dec 15, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants