Slide 1

Slide 1 text

BBM Call Out 101 By Call Out Jakarta Android Team

Slide 2

Slide 2 text

We Have BBM Call Already Why We Need Call Out ? https://giphy.com/gifs/reaction-a5viI92PAF89q

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Why Call Out is Made • Needs to contact family/relatives in areas with internet connectivity issue • Phone call is still main way of people to communicate • Demand for competitive rate of calls

Slide 5

Slide 5 text

What is BBM Call Out • A service to provide BBM account a virtual phone number (VN) • A virtual phone number can be used for voice and SMS, both in and out • Business Cooperation with Smartfren as Telco Provider

Slide 6

Slide 6 text

Requirement To Get VN • Accept ToC • KTP & KK - Indonesian Resident • VN ready to use • Voice/SMS In is free of charge • Voice/SMS Out is chargeable (DANA)

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

VN can Expire • VN will expire if not used to make chargeable call within 90 days • To prevent expiry, make outgoing call or receive incoming call from non Smartfren number • Once expired, we can not get same VN if re-register

Slide 9

Slide 9 text

How Call Out Works

Slide 10

Slide 10 text

Tech? Challenge • Platform Team: • Leverage MediaCallManager • Instead of user uri, use phone number as ID • Backend & Platform Team • Working with 3rd party require effective communication and patience

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

Tech? Challenge • Android Team: • Smartfren require user to give exact location when user access Call Out • Team does not want to reinvent the wheels • Leverage BBMLocationManager

Slide 13

Slide 13 text

http://mathtourist.blogspot.com/2011/05/riding-on-square-wheels.html The Wheels is Unfit

Slide 14

Slide 14 text

Tech? Challenge • BBMLocationManager • Low accuracy • Long interval cache • Using old Android’s Location API

Slide 15

Slide 15 text

Tech? Challenge • Instead of using BBMLocationManager, we decide to use
 Android’s Fused Location Provider API 
 (part of Google Play Services) • Simple API and battery-efficient • Manages underlying location technologies 
 (GPS and Wi-Fi) • Best accuracy possible with no additional power consumption • Provide Last Known Location and Query Location Updates

Slide 16

Slide 16 text

FusedLocationProviderClient // Play Location Services implementation "com.google.android.gms:play-services-location:16.0.0" implementation "com.android.support:support-v4:$support_version" app/build.gradle app/../AndroidManifest.xml

Slide 17

Slide 17 text

FusedLocationProviderClient // Play Location Services implementation "com.google.android.gms:play-services-location:16.0.0" implementation "com.android.support:support-v4:$support_version" app/build.gradle app/../AndroidManifest.xml

Slide 18

Slide 18 text

Implementation typealias LocationData = Pair interface FusedLocationWrapper { fun lastKnownLocation(): Single fun locationUpdates(): Observable }

Slide 19

Slide 19 text

Last (Known) Location class FusedLocationWrapperImpl( private val client: FusedLocationProviderClient ) : FusedLocationWrapper { @SuppressLint("MissingPermission") @MainThread override fun lastKnownLocation(): Single = Single.create { emitter -> client.lastLocation .addOnSuccessListener { location -> location?.let { emitter.onSuccess(it.latitude to it.longitude) } } .addOnFailureListener { emitter.onError(it) } }.subscribeOn(AndroidSchedulers.mainThread())

Slide 20

Slide 20 text

Last (Known) Location • Last Location usually equivalent with user current location • Last Location may return null value when • Location (GPS) is turned off 
 (will clear last location cache if already retrieved) • Device never record location 
 (new device or factory resetted) • Google Play Services restarted on device

Slide 21

Slide 21 text

Location Updates class FusedLocationWrapperImpl( private val client: FusedLocationProviderClient ) : FusedLocationWrapper { . . . override fun lastKnownLocation(): Single = . . . @SuppressLint("MissingPermission") @MainThread override fun locationUpdates(): Observable = Observable.create { emitter -> val request = LocationRequest().apply { interval = 1_000 fastestInterval = 500 priority = LocationRequest.PRIORITY_HIGH_ACCURACY } val callback = object : LocationCallback() { override fun onLocationResult(location: LocationResult?) { location?.lastLocation?.let { emitter.onNext(it.latitude to it.longitude) } } } client.requestLocationUpdates(request, callback, Looper.myLooper()) emitter.setCancellable { client.removeLocationUpdates(callback) } }.subscribeOn(AndroidSchedulers.mainThread())

Slide 22

Slide 22 text

Location Updates class FusedLocationWrapperImpl( private val client: FusedLocationProviderClient ) : FusedLocationWrapper { . . . override fun lastKnownLocation(): Single = . . . @SuppressLint("MissingPermission") @MainThread override fun locationUpdates(): Observable = Observable.create { emitter -> val request = LocationRequest().apply { interval = 1_000 fastestInterval = 500 priority = LocationRequest.PRIORITY_HIGH_ACCURACY } val callback = object : LocationCallback() { override fun onLocationResult(location: LocationResult?) { location?.lastLocation?.let { emitter.onNext(it.latitude to it.longitude) } } } client.requestLocationUpdates(request, callback, Looper.myLooper()) emitter.setCancellable { client.removeLocationUpdates(callback) } }.subscribeOn(AndroidSchedulers.mainThread())

Slide 23

Slide 23 text

Location Request Priority • PRIORITY_BALANCED_POWER_ACCURACY • City block accuracy of approx 100m, • Coarse & consume less power • Likely use Wi-Fi and cell tower positioning

Slide 24

Slide 24 text

Location Request Priority (2) • PRIORITY_HIGH_ACCURACY • Most precise accuracy • Likely use GPS to determine location

Slide 25

Slide 25 text

Location Request Priority (3) • PRIORITY_LOW_POWER • City block accuracy of approx 10km • Coarse & consume less power

Slide 26

Slide 26 text

Location Request Priority (4) • PRIORITY_NO_POWER • Only receive updates other apps trigger location updates

Slide 27

Slide 27 text

Location Updates class FusedLocationWrapperImpl( private val client: FusedLocationProviderClient ) : FusedLocationWrapper { . . . override fun lastKnownLocation(): Single = . . . @SuppressLint("MissingPermission") @MainThread override fun locationUpdates(): Observable = Observable.create { emitter -> val request = LocationRequest().apply { interval = 1_000 fastestInterval = 500 priority = LocationRequest.PRIORITY_HIGH_ACCURACY } val callback = object : LocationCallback() { override fun onLocationResult(location: LocationResult?) { location?.lastLocation?.let { emitter.onNext(it.latitude to it.longitude) } } } client.requestLocationUpdates(request, callback, Looper.myLooper()) emitter.setCancellable { client.removeLocationUpdates(callback) } }.subscribeOn(AndroidSchedulers.mainThread())

Slide 28

Slide 28 text

Location Updates class FusedLocationWrapperImpl( private val client: FusedLocationProviderClient ) : FusedLocationWrapper { . . . override fun lastKnownLocation(): Single = . . . @SuppressLint("MissingPermission") @MainThread override fun locationUpdates(): Observable = Observable.create { emitter -> val request = LocationRequest().apply { interval = 1_000 fastestInterval = 500 priority = LocationRequest.PRIORITY_HIGH_ACCURACY } val callback = object : LocationCallback() { override fun onLocationResult(location: LocationResult?) { location?.lastLocation?.let { emitter.onNext(it.latitude to it.longitude) } } } client.requestLocationUpdates(request, callback, Looper.myLooper()) emitter.setCancellable { client.removeLocationUpdates(callback) } }.subscribeOn(AndroidSchedulers.mainThread())

Slide 29

Slide 29 text

Location Updates class FusedLocationWrapperImpl( private val client: FusedLocationProviderClient ) : FusedLocationWrapper { . . . override fun lastKnownLocation(): Single = . . . @SuppressLint("MissingPermission") @MainThread override fun locationUpdates(): Observable = Observable.create { emitter -> val request = LocationRequest().apply { interval = 1_000 fastestInterval = 500 priority = LocationRequest.PRIORITY_HIGH_ACCURACY } val callback = object : LocationCallback() { override fun onLocationResult(location: LocationResult?) { location?.lastLocation?.let { emitter.onNext(it.latitude to it.longitude) } } } client.requestLocationUpdates(request, callback, Looper.myLooper()) emitter.setCancellable { client.removeLocationUpdates(callback) } }.subscribeOn(AndroidSchedulers.mainThread())

Slide 30

Slide 30 text

Location Updates class FusedLocationWrapperImpl( private val client: FusedLocationProviderClient ) : FusedLocationWrapper { . . . override fun lastKnownLocation(): Single = . . . @SuppressLint("MissingPermission") @MainThread override fun locationUpdates(): Observable = Observable.create { emitter -> val request = LocationRequest().apply { interval = 1_000 fastestInterval = 500 priority = LocationRequest.PRIORITY_HIGH_ACCURACY } val callback = object : LocationCallback() { override fun onLocationResult(location: LocationResult?) { location?.lastLocation?.let { emitter.onNext(it.latitude to it.longitude) } } } client.requestLocationUpdates(request, callback, Looper.myLooper()) emitter.setCancellable { client.removeLocationUpdates(callback) } }.subscribeOn(AndroidSchedulers.mainThread())

Slide 31

Slide 31 text

Location Updates class FusedLocationWrapperImpl( private val client: FusedLocationProviderClient ) : FusedLocationWrapper { . . . override fun lastKnownLocation(): Single = . . . @SuppressLint("MissingPermission") @MainThread override fun locationUpdates(): Observable = Observable.create { emitter -> val request = LocationRequest().apply { interval = 1_000 fastestInterval = 500 priority = LocationRequest.PRIORITY_HIGH_ACCURACY } val callback = object : LocationCallback() { override fun onLocationResult(location: LocationResult?) { location?.lastLocation?.let { emitter.onNext(it.latitude to it.longitude) } } } client.requestLocationUpdates(request, callback, Looper.myLooper()) emitter.setCancellable { client.removeLocationUpdates(callback) } }.subscribeOn(AndroidSchedulers.mainThread())

Slide 32

Slide 32 text

How to use interface LocationGateway { fun currentLocation(): Single } class LocationGatewayImpl( private val fusedLocation: FusedLocationWrapper ) : LocationGateway { override fun currentLocation(): Single = Single.merge( fusedLocation.lastKnownLocation(), fusedLocation.locationUpdates().firstOrError() ).firstOrError() }

Slide 33

Slide 33 text

How to use interface LocationGateway { fun currentLocation(): Single } class LocationGatewayImpl( private val fusedLocation: FusedLocationWrapper ) : LocationGateway { override fun currentLocation(): Single = Single.merge( fusedLocation.lastKnownLocation(), fusedLocation.locationUpdates().firstOrError() ).firstOrError() }

Slide 34

Slide 34 text

Terima Kasih Eko Suhariyadi — @ECHOES Istantyo Triono — @E3C8408B