Skip to content

Commit 6ae18fa

Browse files
committed
Location Feature added #20
clients can now read device location using NETWORK and GPS provider
1 parent ef88ab7 commit 6ae18fa

File tree

4 files changed

+222
-18
lines changed

4 files changed

+222
-18
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
88
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
99
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
10+
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
11+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
1012

1113
<application
1214
android:allowBackup="true"

app/src/main/java/github/umer0586/fragments/customadapters/ConnectionsRecyclerViewAdapter.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.ArrayList;
2020

2121
import github.umer0586.R;
22+
import github.umer0586.sensorserver.LocationRequestInfo;
2223
import github.umer0586.sensorserver.SensorWebSocketServer;
2324

2425
public class ConnectionsRecyclerViewAdapter extends RecyclerView.Adapter<ConnectionsRecyclerViewAdapter.MyViewHolder> {
@@ -91,6 +92,10 @@ else if(webSocket.getAttachment() instanceof ArrayList)
9192
viewHolder.sensorDetails.setText(detail.trim());
9293
}
9394

95+
else if (webSocket.getAttachment() instanceof LocationRequestInfo)
96+
viewHolder.sensorDetails.setText("location (" + ((LocationRequestInfo)webSocket.getAttachment()).getProvider() + " provider )" );
97+
98+
9499

95100
viewHolder.closeConnection.setOnClickListener(v->{
96101

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package github.umer0586.sensorserver;
2+
3+
public class LocationRequestInfo {
4+
5+
private String provider;
6+
7+
public LocationRequestInfo(String provider)
8+
{
9+
this.provider = provider;
10+
}
11+
12+
public String getProvider()
13+
{
14+
return provider;
15+
}
16+
}

app/src/main/java/github/umer0586/sensorserver/SensorWebSocketServer.java

Lines changed: 199 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
package github.umer0586.sensorserver;
22

3+
import android.Manifest;
4+
import android.annotation.SuppressLint;
35
import android.content.Context;
6+
import android.content.pm.PackageManager;
47
import android.hardware.Sensor;
58
import android.hardware.SensorEvent;
69
import android.hardware.SensorEventListener;
710
import android.hardware.SensorManager;
11+
import android.location.Location;
12+
import android.location.LocationListener;
13+
import android.location.LocationManager;
814
import android.net.Uri;
15+
import android.os.Build;
916
import android.os.Handler;
1017
import android.os.HandlerThread;
1118
import android.util.Log;
1219

20+
import androidx.annotation.NonNull;
21+
1322
import org.java_websocket.WebSocket;
1423
import org.java_websocket.handshake.ClientHandshake;
1524
import org.java_websocket.server.WebSocketServer;
@@ -25,13 +34,18 @@
2534
import github.umer0586.util.SensorUtil;
2635

2736

28-
public class SensorWebSocketServer extends WebSocketServer implements SensorEventListener {
37+
public class SensorWebSocketServer extends WebSocketServer implements SensorEventListener, LocationListener {
2938

3039
private static final String TAG = SensorWebSocketServer.class.getName();
3140

41+
42+
private Context context;
43+
3244
private int samplingRate = 200000;//default value normal rate
3345
private static final String CONNECTION_PATH_SINGLE_SENSOR = "/sensor/connect";
3446
private static final String CONNECTION_PATH_MULTIPLE_SENSORS = "/sensors/connect";
47+
private static final String CONNECTION_PATH_LOCATION = "/location";
48+
3549
private static final HashMap<String, Object> response = new HashMap<>();
3650

3751
private HandlerThread handlerThread;
@@ -56,17 +70,22 @@ public class SensorWebSocketServer extends WebSocketServer implements SensorEven
5670
//websocket close codes ranging 4000 - 4999 are for application's custom messages
5771
public static final int CLOSE_CODE_SENSOR_NOT_FOUND = 4001;
5872
public static final int CLOSE_CODE_UNSUPPORTED_REQUEST = 4002;
59-
public static final int CLOSE_CODE_TYPE_PARAMETER_MISSING = 4003;
73+
public static final int CLOSE_CODE_PARAMETER_MISSING = 4003;
6074
public static final int CLOSE_CODE_SERVER_STOPPED = 4004;
6175
public static final int CLOSE_CODE_CONNECTION_CLOSED_BY_APP_USER = 4005;
6276
public static final int CLOSE_CODE_INVALID_JSON_ARRAY = 4006;
6377
public static final int CLOSE_CODE_TOO_FEW_SENSORS = 4007;
6478
public static final int CLOSE_CODE_NO_SENSOR_SPECIFIED = 4008;
79+
public static final int CLOSE_CODE_PERMISSION_DENIED = 4009;
80+
public static final int CLOSE_CODE_INVALID_PROVIDER = 4010;
81+
public static final int CLOSE_CODE_PROVIDER_NOT_FOUND = 4011;
82+
6583

6684

6785
public SensorWebSocketServer(Context context, InetSocketAddress address)
6886
{
6987
super(address);
88+
this.context = context;
7089
sensorManager = (SensorManager) context.getSystemService(context.SENSOR_SERVICE);
7190
sensorUtil = SensorUtil.getInstance(context);
7291
registeredSensors = new ArrayList<>();
@@ -84,23 +103,17 @@ public void onOpen(WebSocket clientWebsocket, ClientHandshake handshake)
84103

85104
// ws://host:port/sensor/connect?type=<sensorType>
86105
if (uri.getPath().equalsIgnoreCase(CONNECTION_PATH_SINGLE_SENSOR))
87-
{
88-
Log.i(TAG, "param type " + uri.getQueryParameter("type"));
89106
handleSingleSensorRequest(uri, clientWebsocket);
107+
//ws://host:port/sensors/connect?types=["type1","type2"...]
108+
else if (uri.getPath().equalsIgnoreCase(CONNECTION_PATH_MULTIPLE_SENSORS))
109+
handleMultiSensorRequest(uri, clientWebsocket);
90110

91-
//ws://host:port/sensors/connect?types=["type1","type2"...]
92-
} else if (uri.getPath().equalsIgnoreCase(CONNECTION_PATH_MULTIPLE_SENSORS))
93-
{
94-
Log.i(TAG, "param types " + uri.getQueryParameter("types"));
95-
handleMultiSensorRequest(uri,clientWebsocket);
111+
else if (uri.getPath().equalsIgnoreCase(CONNECTION_PATH_LOCATION))
112+
handleLocationRequest(uri, clientWebsocket);
113+
114+
else
115+
clientWebsocket.close(CLOSE_CODE_UNSUPPORTED_REQUEST, "unsupported request");
96116

97-
} else
98-
{
99-
String errorMessage = "Unsupported request \n , " +
100-
"use " + CONNECTION_PATH_SINGLE_SENSOR + "?type=<sensorType> for single sensor on single websocket connection and \n" +
101-
" " + CONNECTION_PATH_MULTIPLE_SENSORS + "?types=[\"type1\",\"type2\", . . . ] for multiple sensors on single websocket connection";
102-
clientWebsocket.close(CLOSE_CODE_UNSUPPORTED_REQUEST, errorMessage);
103-
}
104117

105118
}
106119

@@ -112,7 +125,7 @@ private void handleMultiSensorRequest(Uri uri, WebSocket clientWebsocket)
112125
{
113126
if(uri.getQueryParameter("types") == null)
114127
{
115-
clientWebsocket.close(CLOSE_CODE_TYPE_PARAMETER_MISSING,"<Types> parameter required");
128+
clientWebsocket.close(CLOSE_CODE_PARAMETER_MISSING,"<Types> parameter required");
116129
return;
117130
}
118131
List<String> requestedSensorTypes = JsonUtil.readJSONArray(uri.getQueryParameter("types"));
@@ -167,7 +180,7 @@ private void handleSingleSensorRequest(Uri uri, WebSocket clientWebsocket)
167180
//if type param doesn't exit in the query
168181
if(paramType == null)
169182
{
170-
clientWebsocket.close(CLOSE_CODE_TYPE_PARAMETER_MISSING,"<type> param required");
183+
clientWebsocket.close(CLOSE_CODE_PARAMETER_MISSING,"<type> param required");
171184
//do not proceed further
172185
return;
173186
}
@@ -307,6 +320,158 @@ Use requestTriggerSensor(android.hardware.TriggerEventListener, android.hardware
307320
}
308321
}
309322

323+
@SuppressLint("MissingPermission")
324+
private void handleLocationRequest(Uri uri ,WebSocket clientWebsocket)
325+
{
326+
LocationManager locationManager = (LocationManager)context.getSystemService(Context.LOCATION_SERVICE);
327+
328+
if(!hasLocationPermission())
329+
{
330+
clientWebsocket.close(CLOSE_CODE_PERMISSION_DENIED,"App has No permission to access location");
331+
return;
332+
}
333+
334+
String provider = uri.getQueryParameter("provider");
335+
Log.i(TAG, "handleLocationRequest() : provider = " + provider);
336+
337+
if(provider == null)
338+
{
339+
clientWebsocket.close(CLOSE_CODE_PARAMETER_MISSING,"Provider parameter required");
340+
return;
341+
}
342+
343+
344+
if(provider.equalsIgnoreCase("network"))
345+
{
346+
// if device supports network location provider
347+
if (locationManager.getAllProviders().contains(LocationManager.NETWORK_PROVIDER))
348+
{
349+
//If some clients already connected for network location provider then no need to register this server for location updates for Network provider again
350+
if(!locationProviderHasConnections(LocationManager.NETWORK_PROVIDER))
351+
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, this, handlerThread.getLooper());
352+
353+
clientWebsocket.setAttachment(new LocationRequestInfo(LocationManager.NETWORK_PROVIDER));
354+
355+
}// if device does not support network location provider
356+
else
357+
clientWebsocket.close(CLOSE_CODE_PROVIDER_NOT_FOUND,"network provider not found");
358+
}
359+
360+
else if(provider.equalsIgnoreCase("GPS"))
361+
{
362+
// if device supports GPS location provider
363+
if (locationManager.getAllProviders().contains(LocationManager.GPS_PROVIDER))
364+
{
365+
//If some clients already connected for GPS location provider then no need to register this server for location updates for GPS provider again
366+
if(!locationProviderHasConnections(LocationManager.GPS_PROVIDER))
367+
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this, handlerThread.getLooper());
368+
369+
clientWebsocket.setAttachment(new LocationRequestInfo(LocationManager.GPS_PROVIDER));
370+
}
371+
else // if device does not support GPS location provider
372+
clientWebsocket.close(CLOSE_CODE_PROVIDER_NOT_FOUND,"network provider not found");
373+
}
374+
else
375+
clientWebsocket.close(CLOSE_CODE_INVALID_PROVIDER,"Provider must be either GPS or network");
376+
377+
378+
notifyConnectionsChanged();
379+
380+
}
381+
382+
private boolean locationProviderHasConnections(String provider)
383+
{
384+
int providerConnectionCount = 0;
385+
386+
for(WebSocket websocket : getConnections())
387+
{
388+
if( websocket.getAttachment() instanceof LocationRequestInfo )
389+
{
390+
LocationRequestInfo locationRequestInfo = websocket.getAttachment();
391+
if(locationRequestInfo.getProvider().equals(provider))
392+
providerConnectionCount++;
393+
}
394+
}
395+
396+
Log.i(TAG, "connection counts for " + provider + " location provider " + providerConnectionCount);
397+
return providerConnectionCount > 0 ;
398+
}
399+
400+
@Override
401+
public void onLocationChanged(@NonNull Location location)
402+
{
403+
404+
if(!locationProviderHasConnections(location.getProvider()))
405+
{
406+
Log.w(TAG, "onLocationChanged() : " + "Location update received when no client with " + location.getProvider() + " connected" );
407+
}
408+
409+
for(WebSocket websocket : getConnections())
410+
{
411+
if(websocket.getAttachment() instanceof LocationRequestInfo)
412+
{
413+
response.clear();
414+
response.put("longitude",location.getLongitude());
415+
response.put("latitude",location.getLatitude());
416+
response.put("altitude",location.getAltitude());
417+
response.put("bearing",location.getBearing());
418+
response.put("accuracy",location.getAccuracy());
419+
response.put("speed",location.getSpeed());
420+
response.put("time",location.getTime());
421+
response.put("provider",location.getProvider());
422+
423+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
424+
{
425+
response.put("speedAccuracyMetersPerSecond",location.getSpeedAccuracyMetersPerSecond());
426+
response.put("bearingAccuracyDegrees",location.getBearingAccuracyDegrees());
427+
response.put("elapsedRealtimeNanos",location.getElapsedRealtimeNanos());
428+
response.put("verticalAccuracyMeters",location.getVerticalAccuracyMeters());
429+
430+
}
431+
432+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
433+
{
434+
response.put("elapsedRealtimeAgeMillis",location.getElapsedRealtimeAgeMillis());
435+
}
436+
437+
438+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
439+
{
440+
response.put("time",location.getElapsedRealtimeUncertaintyNanos());
441+
}
442+
443+
444+
LocationRequestInfo locationRequestInfo = websocket.getAttachment();
445+
446+
if(locationRequestInfo.getProvider().equals(location.getProvider()))
447+
websocket.send( JsonUtil.toJSON(response) );
448+
}
449+
}
450+
}
451+
452+
@Override
453+
public void onProviderDisabled(@NonNull String provider)
454+
{
455+
Log.i(TAG, "onProviderDisabled() " + provider);
456+
}
457+
458+
@Override
459+
public void onProviderEnabled(@NonNull String provider)
460+
{
461+
Log.i(TAG, "onProviderEnabled() " + provider);
462+
}
463+
464+
private boolean hasLocationPermission()
465+
{
466+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
467+
{
468+
return context.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
469+
}
470+
471+
//prior to android marshmallow dangerous permission are prompt at install time
472+
return true;
473+
}
474+
310475
@Override
311476
public void onClose(WebSocket clientWebsocket, int code, String reason, boolean remote)
312477
{
@@ -325,6 +490,19 @@ else if (clientWebsocket.getAttachment() instanceof ArrayList)
325490
for(Sensor sensor : sensors)
326491
unregisterSensor(sensor);
327492
}
493+
else if(clientWebsocket.getAttachment() instanceof LocationRequestInfo)
494+
{
495+
496+
LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
497+
LocationRequestInfo locationRequestInfo = clientWebsocket.getAttachment();
498+
499+
// unregister this server for location updates from specific provider ...
500+
// when there are no clients associated with that provider
501+
if(!locationProviderHasConnections(locationRequestInfo.getProvider()))
502+
locationManager.removeUpdates(this);
503+
504+
}
505+
notifyConnectionsChanged();
328506

329507

330508
}
@@ -423,6 +601,9 @@ public void onStart()
423601
public void stop() throws IOException, InterruptedException
424602
{
425603
closeAllConnections();
604+
LocationManager locationManager = (LocationManager)context.getSystemService(Context.LOCATION_SERVICE);
605+
locationManager.removeUpdates(this);
606+
426607
super.stop();
427608
Log.d(TAG, "stop() called");
428609

0 commit comments

Comments
 (0)