3. public static final String CODENAME_ONE_PUSH_KEY = "----";
public static final String GOOGLE_PUSH_AUTH_KEY = "----";
public static final String APNS_DEV_PUSH_CERT = "----";
public static final String APNS_PROD_PUSH_CERT = "----";
public static final String APNS_DEV_PUSH_PASS = "----";
public static final String APNS_PROD_PUSH_PASS = "----";
public static final boolean APNS_PRODUCTION = false;
Globals
In order to do this I had to make some changes to the Websocket protocol in the LocationService class. But first we need some additional variables in the Globals class.
I'll go into more details on those values in the next chapter but for now we need the variables only. All of these API's require developer keys which you can obtain from
their respective websites. I've edited the Globals class to include these new keys required by the 3 API's
Right now we can leave these all blank and get to them in the next chapter.
4. private static final short MESSAGE_TYPE_DRIVER_FOUND = 4;
private static final short HAILING_OFF = 0;
private static final short HAILING_ON = 1;
private static final short HAILING_TURN_OFF = 2;
private static LocationService instance;
private int hailing;
private Motion halingRadius;
private String from;
private String to;
private List<String> notificationList;
private long driverId;
private CarAdded driverFound;
LocationService
Let's move to the LocationService class and the variables I had to add
When a driver accepts our hail the server sends a special message indicating that the hail was accepted
5. private static final short MESSAGE_TYPE_DRIVER_FOUND = 4;
private static final short HAILING_OFF = 0;
private static final short HAILING_ON = 1;
private static final short HAILING_TURN_OFF = 2;
private static LocationService instance;
private int hailing;
private Motion halingRadius;
private String from;
private String to;
private List<String> notificationList;
private long driverId;
private CarAdded driverFound;
LocationService
Previously we had 2 modes for polling the server for searching and not searching. I added a 3rd mode that allows us to disable hailing
6. private static final short MESSAGE_TYPE_DRIVER_FOUND = 4;
private static final short HAILING_OFF = 0;
private static final short HAILING_ON = 1;
private static final short HAILING_TURN_OFF = 2;
private static LocationService instance;
private int hailing;
private Motion halingRadius;
private String from;
private String to;
private List<String> notificationList;
private long driverId;
private CarAdded driverFound;
LocationService
I've made the LocationService into a singleton
7. private static final short MESSAGE_TYPE_DRIVER_FOUND = 4;
private static final short HAILING_OFF = 0;
private static final short HAILING_ON = 1;
private static final short HAILING_TURN_OFF = 2;
private static LocationService instance;
private int hailing;
private Motion halingRadius;
private String from;
private String to;
private List<String> notificationList;
private long driverId;
private CarAdded driverFound;
LocationService
These represent whether we are hailing and if so to what radius? We use a Motion object to get a growing radius that will stretch over time to encapsulate a wider region
for hailing
8. private static final short MESSAGE_TYPE_DRIVER_FOUND = 4;
private static final short HAILING_OFF = 0;
private static final short HAILING_ON = 1;
private static final short HAILING_TURN_OFF = 2;
private static LocationService instance;
private int hailing;
private Motion halingRadius;
private String from;
private String to;
private List<String> notificationList;
private long driverId;
private CarAdded driverFound;
LocationService
Our source and destination values which we need to broadcast a hail
9. private static final short MESSAGE_TYPE_DRIVER_FOUND = 4;
private static final short HAILING_OFF = 0;
private static final short HAILING_ON = 1;
private static final short HAILING_TURN_OFF = 2;
private static LocationService instance;
private int hailing;
private Motion halingRadius;
private String from;
private String to;
private List<String> notificationList;
private long driverId;
private CarAdded driverFound;
LocationService
When we send a push notification to a car we need to make sure we didn't already notify it
10. private static final short MESSAGE_TYPE_DRIVER_FOUND = 4;
private static final short HAILING_OFF = 0;
private static final short HAILING_ON = 1;
private static final short HAILING_TURN_OFF = 2;
private static LocationService instance;
private int hailing;
private Motion halingRadius;
private String from;
private String to;
private List<String> notificationList;
private long driverId;
private CarAdded driverFound;
LocationService
The unique id of the driver we've found
11. private static final short MESSAGE_TYPE_DRIVER_FOUND = 4;
private static final short HAILING_OFF = 0;
private static final short HAILING_ON = 1;
private static final short HAILING_TURN_OFF = 2;
private static LocationService instance;
private int hailing;
private Motion halingRadius;
private String from;
private String to;
private List<String> notificationList;
private long driverId;
private CarAdded driverFound;
LocationService
This is the callback we invoke when a driver is found
12. if(ll == lon && lt == lat && dir == direction && hailing == HAILING_OFF) {
// no need to do an update
return;
}
sendLocationUpdate
Now that these are out of the way lets look at the other things that need doing. We changed the way we handle the communication protocol by and we added some
additional details.
First we need to ignore location changes when doing hailing which we can do by adding that condition.
13. dos.writeDouble(lat);
dos.writeDouble(lon);
dos.writeFloat(dir);
if(hailing == HAILING_ON) {
dos.writeDouble(((double)halingRadius.getValue()) / 1000.0);
dos.writeByte(HAILING_ON);
byte[] fromBytes = from.getBytes("UTF-8");
byte[] toBytes = to.getBytes("UTF-8");
dos.writeShort(fromBytes.length);
dos.write(fromBytes);
dos.writeShort(toBytes.length);
dos.write(toBytes);
} else {
dos.writeDouble(1);
dos.writeByte(hailing);
if(hailing == HAILING_TURN_OFF) {
hailing = HAILING_OFF;
}
}
dos.flush();
send(bos.toByteArray());
} catch(IOException err) {
Log.e(err);
}
sendLocationUpdate
Next we need to change the protocol a little bit
This was previously limited to 0 only and now we check if we are in hailing mode
15. dos.writeDouble(lat);
dos.writeDouble(lon);
dos.writeFloat(dir);
if(hailing == HAILING_ON) {
dos.writeDouble(((double)halingRadius.getValue()) / 1000.0);
dos.writeByte(HAILING_ON);
byte[] fromBytes = from.getBytes("UTF-8");
byte[] toBytes = to.getBytes("UTF-8");
dos.writeShort(fromBytes.length);
dos.write(fromBytes);
dos.writeShort(toBytes.length);
dos.write(toBytes);
} else {
dos.writeDouble(1);
dos.writeByte(hailing);
if(hailing == HAILING_TURN_OFF) {
hailing = HAILING_OFF;
}
}
dos.flush();
send(bos.toByteArray());
} catch(IOException err) {
Log.e(err);
}
sendLocationUpdate
We send the from/to values as UTF-8 encoded strings which allows us to communicate locale specific locations
16. dos.writeDouble(lat);
dos.writeDouble(lon);
dos.writeFloat(dir);
if(hailing == HAILING_ON) {
dos.writeDouble(((double)halingRadius.getValue()) / 1000.0);
dos.writeByte(HAILING_ON);
byte[] fromBytes = from.getBytes("UTF-8");
byte[] toBytes = to.getBytes("UTF-8");
dos.writeShort(fromBytes.length);
dos.write(fromBytes);
dos.writeShort(toBytes.length);
dos.write(toBytes);
} else {
dos.writeDouble(1);
dos.writeByte(hailing);
if(hailing == HAILING_TURN_OFF) {
hailing = HAILING_OFF;
}
}
dos.flush();
send(bos.toByteArray());
} catch(IOException err) {
Log.e(err);
}
sendLocationUpdate
When we turn off hailing in the server it's a "one time thing". After it's off we can go back to the regular mode
17. dos.writeDouble(lat);
dos.writeDouble(lon);
dos.writeFloat(dir);
if(hailing == HAILING_ON) {
dos.writeDouble(((double)halingRadius.getValue()) / 1000.0);
dos.writeByte(HAILING_ON);
byte[] fromBytes = from.getBytes("UTF-8");
byte[] toBytes = to.getBytes("UTF-8");
dos.writeShort(fromBytes.length);
dos.write(fromBytes);
dos.writeShort(toBytes.length);
dos.write(toBytes);
} else {
dos.writeDouble(1);
dos.writeByte(hailing);
if(hailing == HAILING_TURN_OFF) {
hailing = HAILING_OFF;
}
}
dos.flush();
send(bos.toByteArray());
} catch(IOException err) {
Log.e(err);
}
sendLocationUpdate
This isn't likely as this is a RAM based stream
18. @Override
protected void onMessage(byte[] bytes) {
try {
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes));
short response = dis.readShort();
if(response == MESSAGE_TYPE_DRIVER_FOUND) {
driverId = dis.readLong();
User u = cars.get(driverId);
if(u == null) {
u = new User().id.set(driverId);
cars.put(driverId, u);
}
u.car.set(dis.readUTF()).
givenName.set(dis.readUTF()).
surname.set(dis.readUTF()).
currentRating.set(dis.readFloat());
final User finalUser = u;
callSerially(() -> driverFound.carAdded(finalUser));
return;
}
int size = dis.readInt();
List<String> sendPush = null;
for(int iter = 0 ; iter < size ; iter++) {
onMessage
We also need to handle message reception code.
This is a new special case that provides us with details on the driver that picked up the ride. We are provided with the driver id, car & name. Notice we need the finalUser
variable since car might change and a value that can change can't be passed to an inner class or lambda in Java.
19. @Override
protected void onMessage(byte[] bytes) {
try {
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes));
short response = dis.readShort();
if(response == MESSAGE_TYPE_DRIVER_FOUND) {
driverId = dis.readLong();
User u = cars.get(driverId);
if(u == null) {
u = new User().id.set(driverId);
cars.put(driverId, u);
}
u.car.set(dis.readUTF()).
givenName.set(dis.readUTF()).
surname.set(dis.readUTF()).
currentRating.set(dis.readFloat());
final User finalUser = u;
callSerially(() -> driverFound.carAdded(finalUser));
return;
}
int size = dis.readInt();
List<String> sendPush = null;
for(int iter = 0 ; iter < size ; iter++) {
onMessage
This is a list of push keys who we should notify
20. List<String> sendPush = null;
for(int iter = 0 ; iter < size ; iter++) {
long id = dis.readLong();
User car = cars.get(id);
if(car == null) {
car = new User().
id.set(id).
latitude.set(dis.readDouble()).
longitude.set(dis.readDouble()).
direction.set(dis.readFloat()).
pushToken.set(dis.readUTF());
cars.put(id, car);
User finalCar = car;
callSerially(() -> carCallback.carAdded(finalCar));
} else {
car.latitude.set(dis.readDouble()).
longitude.set(dis.readDouble()).
direction.set(dis.readFloat()).
pushToken.set(dis.readUTF());
}
if(hailing==HAILING_ON && response==MESSAGE_TYPE_AVAILBLE_DRIVER_POSITIONS){
if(!notificationList.contains(car.pushToken.get())) {
notificationList.add(car.pushToken.get());
onMessage
I added a push token to the driver details so we can send a push message to a specific driver
21. if(hailing==HAILING_ON && response==MESSAGE_TYPE_AVAILBLE_DRIVER_POSITIONS){
if(!notificationList.contains(car.pushToken.get())) {
notificationList.add(car.pushToken.get());
if(sendPush == null) {
sendPush = new ArrayList<>();
}
sendPush.add(car.pushToken.get());
}
}
}
if(sendPush != null) {
String[] devices = new String[sendPush.size()];
sendPush.toArray(devices);
String apnsCert = APNS_DEV_PUSH_CERT;
String apnsPass = APNS_DEV_PUSH_PASS;
if(APNS_PRODUCTION) {
apnsCert = APNS_PROD_PUSH_CERT;
apnsPass = APNS_PROD_PUSH_PASS;
}
new Push(CODENAME_ONE_PUSH_KEY,
"#" + UserService.getUser().id.getLong() +
";Ride pending from: " + from + " to: " + to,
devices).
onMessage
If the car wasn't notified yet add it to the list of cars that we should notify
22. if(hailing==HAILING_ON && response==MESSAGE_TYPE_AVAILBLE_DRIVER_POSITIONS){
if(!notificationList.contains(car.pushToken.get())) {
notificationList.add(car.pushToken.get());
if(sendPush == null) {
sendPush = new ArrayList<>();
}
sendPush.add(car.pushToken.get());
}
}
}
if(sendPush != null) {
String[] devices = new String[sendPush.size()];
sendPush.toArray(devices);
String apnsCert = APNS_DEV_PUSH_CERT;
String apnsPass = APNS_DEV_PUSH_PASS;
if(APNS_PRODUCTION) {
apnsCert = APNS_PROD_PUSH_CERT;
apnsPass = APNS_PROD_PUSH_PASS;
}
new Push(CODENAME_ONE_PUSH_KEY,
"#" + UserService.getUser().id.getLong() +
";Ride pending from: " + from + " to: " + to,
devices).
onMessage
We send the push message in a batch to speed this up
23. }
}
if(sendPush != null) {
String[] devices = new String[sendPush.size()];
sendPush.toArray(devices);
String apnsCert = APNS_DEV_PUSH_CERT;
String apnsPass = APNS_DEV_PUSH_PASS;
if(APNS_PRODUCTION) {
apnsCert = APNS_PROD_PUSH_CERT;
apnsPass = APNS_PROD_PUSH_PASS;
}
new Push(CODENAME_ONE_PUSH_KEY,
"#" + UserService.getUser().id.getLong() +
";Ride pending from: " + from + " to: " + to,
devices).
pushType(3).
apnsAuth(apnsCert, apnsPass, APNS_PRODUCTION).
gcmAuth(GOOGLE_PUSH_AUTH_KEY).sendAsync();
}
} catch(IOException err) {
Log.e(err);
}
}
onMessage
We send push type 3 which includes a data payload (the first section) and a visual payload which you see after the semicolon
24. public final Property<String, User> pushToken = new Property<>("pushToken");
private final PropertyIndex idx = new PropertyIndex(this, "User", id, givenName,
surname, phone, email, facebookId, googleId, driver, car, currentRating,
latitude, longitude, direction, authToken, password, pushToken);
User
Before we can compile that code we need to add a pushToken attribute to the User class
25. public static void hailRide(String from, String to, CarAdded callback) {
instance.driverFound = callback;
instance.from = from;
instance.to = to;
instance.notificationList = new ArrayList<>();
instance.halingRadius = Motion.createLinearMotion(500, 2000, 30000);
instance.halingRadius.start();
instance.hailing = HAILING_ON;
instance.server.sendLocationUpdate();
}
hailRide
Finally we have the hailRide method which is relatively simple. There isn't much we need to cover here. It just initializes the variables and starts the Motion object for the
expanding radius.
This should conclude the client side of the hailing process and now we need to address the server side…