Skip to content

Commit

Permalink
Added LocationServicesOkObservable. ( #123 )
Browse files Browse the repository at this point in the history
Reviewers: pawel.urban

Reviewed By: pawel.urban

Differential Revision: https://phabricator.polidea.com/D2142
  • Loading branch information
dariuszseweryn committed Jan 30, 2017
1 parent baefa17 commit 3ad42ec
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 18 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ RxBleClient.setLogLevel(RxBleLog.DEBUG);
### Error handling
Every error you may encounter is provided via onError callback. Each public method has JavaDoc explaining possible errors.

### Helpers
We encourage you to check the package `com.polidea.rxandroidble.helpers` which contains handy reactive wrappers for some typical use-cases.

## More examples

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.polidea.rxandroidble.helpers;


import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.LocationManager;
import android.support.annotation.NonNull;
import com.polidea.rxandroidble.internal.util.CheckerLocationPermission;
import com.polidea.rxandroidble.internal.util.CheckerLocationProvider;
import com.polidea.rxandroidble.internal.util.LocationServicesStatus;
import com.polidea.rxandroidble.internal.util.ProviderApplicationTargetSdk;
import com.polidea.rxandroidble.internal.util.ProviderDeviceSdk;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import rx.Emitter;
import rx.Observable;
import rx.functions.Action1;
import rx.functions.Cancellable;
import rx.internal.operators.OnSubscribeFromEmitter;

/**
* An Observable that emits true when {@link com.polidea.rxandroidble.RxBleClient#scanBleDevices(UUID...)} would not
* emit {@link com.polidea.rxandroidble.exceptions.BleScanException} with a reason
* {@link com.polidea.rxandroidble.exceptions.BleScanException#LOCATION_SERVICES_DISABLED}
*/
public class LocationServicesOkObservable extends Observable<Boolean> {

public static LocationServicesOkObservable createInstance(@NonNull Context context) {
final Context applicationContext = context.getApplicationContext();
final LocationManager locationManager = (LocationManager) applicationContext.getSystemService(Context.LOCATION_SERVICE);
final ProviderDeviceSdk providerDeviceSdk = new ProviderDeviceSdk();
final ProviderApplicationTargetSdk providerApplicationTargetSdk = new ProviderApplicationTargetSdk(applicationContext);
final CheckerLocationPermission checkerLocationPermission = new CheckerLocationPermission(applicationContext);
final CheckerLocationProvider checkerLocationProvider = new CheckerLocationProvider(locationManager);
final LocationServicesStatus locationServicesStatus = new LocationServicesStatus(
checkerLocationProvider,
checkerLocationPermission,
providerDeviceSdk,
providerApplicationTargetSdk
);
return new LocationServicesOkObservable(applicationContext, locationServicesStatus);
}

LocationServicesOkObservable(@NonNull Context context, @NonNull LocationServicesStatus locationServicesStatus) {
super(new OnSubscribeFromEmitter<>(
new Action1<Emitter<Boolean>>() {
@Override
public void call(Emitter<Boolean> emitter) {
final boolean locationProviderOk = locationServicesStatus.isLocationProviderOk();
final AtomicBoolean locationProviderOkAtomicBoolean = new AtomicBoolean(locationProviderOk);
emitter.onNext(locationProviderOk);

BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final boolean newLocationProviderOkValue = locationServicesStatus.isLocationProviderOk();
final boolean valueChanged = locationProviderOkAtomicBoolean
.compareAndSet(!newLocationProviderOkValue, newLocationProviderOkValue);
if (valueChanged) {
emitter.onNext(newLocationProviderOkValue);
}
}
};

context.registerReceiver(broadcastReceiver, new IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION));
emitter.setCancellation(new Cancellable() {
@Override
public void cancel() throws Exception {
context.unregisterReceiver(broadcastReceiver);
}
});
}
},
Emitter.BackpressureMode.LATEST
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.os.Process;

public class CheckerLocationPermission {

Expand All @@ -15,14 +14,21 @@ public CheckerLocationPermission(Context context) {
this.context = context;
}

@RequiresApi(api = Build.VERSION_CODES.M)
public boolean isLocationPermissionGranted() {
boolean isLocationPermissionGranted() {
return isPermissionGranted(Manifest.permission.ACCESS_COARSE_LOCATION)
|| isPermissionGranted(Manifest.permission.ACCESS_FINE_LOCATION);
}

@RequiresApi(api = Build.VERSION_CODES.M)
/**
* Copied from android.support.v4.content.ContextCompat for backwards compatibility
* @param permission the permission to check
* @return true is granted
*/
private boolean isPermissionGranted(String permission) {
return context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
if (permission == null) {
throw new IllegalArgumentException("permission is null");
}

return context.checkPermission(permission, android.os.Process.myPid(), Process.myUid()) == PackageManager.PERMISSION_GRANTED;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.polidea.rxandroidble.internal.util;

import android.annotation.TargetApi;
import android.os.Build;

public class LocationServicesStatus {
Expand All @@ -26,20 +25,11 @@ public LocationServicesStatus(
}

public boolean isLocationPermissionOk() {
return !isLocationPermissionGrantedRequired() || isLocationPermissionGranted();
return !isLocationPermissionGrantedRequired() || checkerLocationPermission.isLocationPermissionGranted();
}

public boolean isLocationProviderOk() {
return !isLocationProviderEnabledRequired() || isLocationProviderEnabled();
}

@TargetApi(Build.VERSION_CODES.M)
private boolean isLocationPermissionGranted() {
return checkerLocationPermission.isLocationPermissionGranted();
}

private boolean isLocationProviderEnabled() {
return checkerLocationProvider.isLocationProviderEnabled();
return !isLocationProviderEnabledRequired() || checkerLocationProvider.isLocationProviderEnabled();
}

private boolean isLocationPermissionGrantedRequired() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package com.polidea.rxandroidble.helpers

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.location.LocationManager
import com.polidea.rxandroidble.internal.util.LocationServicesStatus
import org.robolectric.annotation.Config
import org.robospock.RoboSpecification
import rx.Subscription
import rx.observers.TestSubscriber

@Config(manifest = Config.NONE)
class LocationServicesOkObservableTest extends RoboSpecification {
def contextMock = Mock Context
def mockLocationServicesStatus = Mock LocationServicesStatus
def objectUnderTest = new LocationServicesOkObservable(contextMock, mockLocationServicesStatus)
BroadcastReceiver registeredReceiver

def setup() {
contextMock.getApplicationContext() >> contextMock
}

def "should register to correct receiver on subscribe"() {

given:
mockLocationServicesStatus.isLocationProviderOk() >> true

when:
objectUnderTest.subscribe()

then:
1 * contextMock.registerReceiver(!null, {
it.hasAction(LocationManager.PROVIDERS_CHANGED_ACTION)
})
}

def "should unregister after observable was unsubscribed"() {

given:
mockLocationServicesStatus.isLocationProviderOk() >> true
shouldCaptureRegisteredReceiver()
Subscription subscription = objectUnderTest.subscribe()

when:
subscription.unsubscribe()

then:
1 * contextMock.unregisterReceiver(registeredReceiver)
}

def "should emit what LocationServicesStatus.isLocationProviderOk() returns on subscribe and on next broadcasts"() {

given:
shouldCaptureRegisteredReceiver()
mockLocationServicesStatus.isLocationProviderOk() >>> [true, false, true]
TestSubscriber<Boolean> testSubscriber = new TestSubscriber<>()

when:
objectUnderTest.subscribe(testSubscriber)

then:
testSubscriber.assertValue(true)

when:
postStateChangeBroadcast()

then:
testSubscriber.assertValues(true, false)

when:
postStateChangeBroadcast()

then:
testSubscriber.assertValues(true, false, true)
}

def "should not emit what LocationServicesStatus.isLocationProviderOk() returns on next broadcasts if the value does not change"() {

given:
shouldCaptureRegisteredReceiver()
mockLocationServicesStatus.isLocationProviderOk() >>> [false, false, true, true, false, false]
TestSubscriber<Boolean> testSubscriber = new TestSubscriber<>()

when:
objectUnderTest.subscribe(testSubscriber)

then:
testSubscriber.assertValue(false)

when:
postStateChangeBroadcast()

then:
testSubscriber.assertValue(false)

when:
postStateChangeBroadcast()

then:
testSubscriber.assertValues(false, true)

when:
postStateChangeBroadcast()

then:
testSubscriber.assertValues(false, true)

when:
postStateChangeBroadcast()

then:
testSubscriber.assertValues(false, true, false)

when:
postStateChangeBroadcast()

then:
testSubscriber.assertValues(false, true, false)
}

public postStateChangeBroadcast() {
def intent = new Intent(LocationManager.PROVIDERS_CHANGED_ACTION)
registeredReceiver.onReceive(contextMock, intent)
}

public BroadcastReceiver shouldCaptureRegisteredReceiver() {
_ * contextMock.registerReceiver({
BroadcastReceiver receiver ->
this.registeredReceiver = receiver
}, _)
}
}

0 comments on commit 3ad42ec

Please sign in to comment.