diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
new file mode 100644
index 00000000..44943151
--- /dev/null
+++ b/.idea/deploymentTargetDropDown.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 9a237764..6ddcda74 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,5 +1,6 @@
+
@@ -48,7 +49,7 @@
-
+
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
deleted file mode 100644
index 7f68460d..00000000
--- a/.idea/runConfigurations.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index ae66a799..09a88463 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -60,6 +60,7 @@ dependencies {
// Room
implementation "androidx.room:room-runtime:2.3.0"
+ implementation 'com.google.android.gms:play-services:11.8.0'
annotationProcessor "androidx.room:room-compiler:2.3.0"
// Parsing
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1c3392df..40325a72 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -5,10 +5,13 @@
-
+
+
+
+
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/schneewittchen/rosandroid/widgets/location/LocationData.java b/app/src/main/java/com/schneewittchen/rosandroid/widgets/location/LocationData.java
new file mode 100644
index 00000000..e6a04f30
--- /dev/null
+++ b/app/src/main/java/com/schneewittchen/rosandroid/widgets/location/LocationData.java
@@ -0,0 +1,46 @@
+package com.schneewittchen.rosandroid.widgets.location;
+
+import com.schneewittchen.rosandroid.model.repositories.rosRepo.node.BaseData;
+import com.schneewittchen.rosandroid.model.entities.widgets.BaseEntity;
+
+import org.ros.internal.message.Message;
+import org.ros.node.topic.Publisher;
+
+import sensor_msgs.NavSatFix;
+
+
+/**
+ * TODO: Description
+ *
+ * @author Gennaro Raiola
+ * @version 0.0.1
+ * @created on 19.11.22
+ */
+
+public class LocationData extends BaseData {
+
+ public double latitude;
+ public double longitude;
+ public double altitude;
+ public String provider;
+
+ public LocationData(double latitude, double longitude, double altitude, String provider) {
+ this.latitude = latitude;
+ this.longitude = longitude;
+ this.altitude = altitude;
+ this.provider = provider;
+ }
+
+ @Override
+ public Message toRosMessage(Publisher publisher, BaseEntity widget) {
+
+ sensor_msgs.NavSatFix message = (NavSatFix) publisher.newMessage();
+
+ message.getHeader().setFrameId(provider);
+ message.setLatitude(latitude);
+ message.setLongitude(longitude);
+ message.setAltitude(altitude);
+
+ return message;
+ }
+}
diff --git a/app/src/main/java/com/schneewittchen/rosandroid/widgets/location/LocationDetailVH.java b/app/src/main/java/com/schneewittchen/rosandroid/widgets/location/LocationDetailVH.java
new file mode 100644
index 00000000..5d1a020a
--- /dev/null
+++ b/app/src/main/java/com/schneewittchen/rosandroid/widgets/location/LocationDetailVH.java
@@ -0,0 +1,66 @@
+package com.schneewittchen.rosandroid.widgets.location;
+
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.Spinner;
+
+import com.schneewittchen.rosandroid.R;
+import com.schneewittchen.rosandroid.model.entities.widgets.BaseEntity;
+import com.schneewittchen.rosandroid.ui.views.details.PublisherWidgetViewHolder;
+import com.schneewittchen.rosandroid.utility.Utils;
+
+import java.util.Collections;
+import java.util.List;
+
+import sensor_msgs.NavSatFix;
+
+/**
+ * TODO: Description
+ *
+ * @author Gennaro Raiola
+ * @version 0.0.1
+ * @created on 19.11.22
+ */
+public class LocationDetailVH extends PublisherWidgetViewHolder {
+
+ private EditText textText;
+ private Spinner rotationSpinner;
+ private ArrayAdapter rotationAdapter;
+
+ @Override
+ public void initView(View view) {
+ textText = view.findViewById(R.id.btnTextTypeText);
+ rotationSpinner = view.findViewById(R.id.btnTextRotation);
+
+ // Init spinner
+ rotationAdapter = ArrayAdapter.createFromResource(view.getContext(),
+ R.array.button_rotation, android.R.layout.simple_spinner_dropdown_item);
+
+ rotationSpinner.setAdapter(rotationAdapter);
+ }
+
+ @Override
+ public void bindEntity(BaseEntity entity) {
+ LocationEntity locationEntity = (LocationEntity) entity;
+
+ textText.setText(locationEntity.text);
+ String degrees = Utils.numberToDegrees(locationEntity.rotation);
+ rotationSpinner.setSelection(rotationAdapter.getPosition(degrees));
+ }
+
+ @Override
+ public void updateEntity(BaseEntity entity) {
+ LocationEntity locationEntity = (LocationEntity) entity;
+
+ locationEntity.text = textText.getText().toString();
+ String degrees = rotationSpinner.getSelectedItem().toString();
+ locationEntity.rotation = Utils.degreesToNumber(degrees);
+ }
+
+ @Override
+ public List getTopicTypes() {
+ return Collections.singletonList(NavSatFix._TYPE);
+ }
+
+}
diff --git a/app/src/main/java/com/schneewittchen/rosandroid/widgets/location/LocationEntity.java b/app/src/main/java/com/schneewittchen/rosandroid/widgets/location/LocationEntity.java
new file mode 100644
index 00000000..b1adeae3
--- /dev/null
+++ b/app/src/main/java/com/schneewittchen/rosandroid/widgets/location/LocationEntity.java
@@ -0,0 +1,32 @@
+package com.schneewittchen.rosandroid.widgets.location;
+
+import com.schneewittchen.rosandroid.model.entities.widgets.PublisherWidgetEntity;
+import com.schneewittchen.rosandroid.model.repositories.rosRepo.message.Topic;
+
+import sensor_msgs.NavSatFix;
+
+/**
+ * TODO: Description
+ *
+ * @author Gennaro Raiola
+ * @version 0.0.1
+ * @created on 19.11.22
+ */
+
+public class LocationEntity extends PublisherWidgetEntity {
+
+ public String text;
+ public int rotation;
+ public boolean buttonPressed;
+
+ public LocationEntity() {
+ this.width = 4;
+ this.height = 4;
+ this.topic = new Topic("location", NavSatFix._TYPE);
+ this.immediatePublish = true;
+ //this.publishRate = 20f;
+ this.text = "Publish phone's location to ROS";
+ this.rotation = 0;
+ this.buttonPressed = false;
+ }
+}
diff --git a/app/src/main/java/com/schneewittchen/rosandroid/widgets/location/LocationView.java b/app/src/main/java/com/schneewittchen/rosandroid/widgets/location/LocationView.java
new file mode 100644
index 00000000..6211783f
--- /dev/null
+++ b/app/src/main/java/com/schneewittchen/rosandroid/widgets/location/LocationView.java
@@ -0,0 +1,198 @@
+package com.schneewittchen.rosandroid.widgets.location;
+
+import static com.google.android.gms.location.LocationServices.getFusedLocationProviderClient;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Looper;
+import android.text.Layout;
+import android.text.StaticLayout;
+import android.text.TextPaint;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import androidx.annotation.Nullable;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
+import com.google.android.gms.location.LocationCallback;
+import com.google.android.gms.location.LocationRequest;
+import com.google.android.gms.location.LocationResult;
+import com.schneewittchen.rosandroid.R;
+import com.schneewittchen.rosandroid.ui.views.widgets.PublisherWidgetView;
+
+import android.Manifest;
+import android.content.pm.PackageManager;
+import android.util.Log;
+import android.view.WindowManager;
+
+import java.util.ArrayList;
+
+
+/**
+ * TODO: Description
+ *
+ * @author Gennaro Raiola
+ * @version 0.0.1
+ * @created on 19.11.22
+ */
+public class LocationView extends PublisherWidgetView {
+
+ public static final String TAG = LocationView.class.getSimpleName();
+
+ Context context;
+
+ Paint buttonPaint;
+ TextPaint textPaint;
+ StaticLayout staticLayout;
+
+ LocationRequest locationRequest;
+ double altitude;
+ double latitude;
+ double longitude;
+ String provider;
+
+
+ public LocationView(Context context) {
+ super(context);
+ this.context = context;
+ init();
+ }
+
+ public LocationView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ this.context = context;
+ init();
+ }
+
+ private void init() {
+
+ ((Activity) getContext()).getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+ LocationEntity entity = (LocationEntity) widgetEntity;
+
+ buttonPaint = new Paint();
+ buttonPaint.setColor(getResources().getColor(R.color.colorPrimary));
+ buttonPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+
+ textPaint = new TextPaint();
+ textPaint.setColor(Color.BLACK);
+ textPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+ textPaint.setTextSize(26 * getResources().getDisplayMetrics().density);
+
+ locationRequest = new LocationRequest();
+ locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
+ //long interval = (long)(1.0/entity.publishRate)*1000;
+ //Log.i(TAG,"INTERVAL - --------------------" + interval);
+ long interval = 500;
+ locationRequest.setInterval(interval);
+ locationRequest.setFastestInterval(interval);
+ locationRequest.setSmallestDisplacement(0);
+ if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
+ ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
+ ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED ) {
+ requestPermissionsIfNecessary(new String[]{
+ Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION
+ });
+ }
+ getFusedLocationProviderClient(context).requestLocationUpdates(locationRequest, new LocationCallback() {
+ @Override
+ public void onLocationResult(LocationResult locationResult) {
+ provider = locationResult.getLastLocation().getProvider();
+ altitude = locationResult.getLastLocation().getAltitude();
+ latitude = locationResult.getLastLocation().getLatitude();
+ longitude = locationResult.getLastLocation().getLongitude();
+ publishCoordinates();
+ }
+ },
+ Looper.myLooper());
+
+ }
+
+ private void requestPermissionsIfNecessary(String[] permissions) {
+ ArrayList permissionsToRequest = new ArrayList<>();
+ for (String permission : permissions) {
+ if (ContextCompat.checkSelfPermission(this.getContext(), permission)
+ != PackageManager.PERMISSION_GRANTED) {
+ // Permission is not granted
+ permissionsToRequest.add(permission);
+ }
+ }
+ }
+
+ private void changeState(boolean pressed) {
+ LocationEntity entity = (LocationEntity) widgetEntity;
+ entity.buttonPressed = pressed;
+ invalidate();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (this.editMode) {
+ return super.onTouchEvent(event);
+ }
+
+ LocationEntity entity = (LocationEntity) widgetEntity;
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ if(entity.buttonPressed)
+ changeState(false);
+ else
+ changeState(true);
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ float width = getWidth();
+ float height = getHeight();
+ float textLayoutWidth = width;
+
+ LocationEntity entity = (LocationEntity) widgetEntity;
+
+ if (entity.rotation == 90 || entity.rotation == 270) {
+ textLayoutWidth = height;
+ }
+
+ if(!entity.buttonPressed)
+ buttonPaint.setColor(getResources().getColor(R.color.colorPrimary));
+ else
+ buttonPaint.setColor(getResources().getColor(R.color.color_attention));
+
+ canvas.drawRect(new Rect(0, 0, (int) width, (int) height), buttonPaint);
+
+ staticLayout = new StaticLayout(entity.text,
+ textPaint,
+ (int) textLayoutWidth,
+ Layout.Alignment.ALIGN_CENTER,
+ 1.0f,
+ 0,
+ false);
+ canvas.save();
+ canvas.rotate(entity.rotation, width / 2, height / 2);
+ canvas.translate(((width / 2) - staticLayout.getWidth() / 2), height / 2 - staticLayout.getHeight() / 2);
+ staticLayout.draw(canvas);
+ canvas.restore();
+ }
+
+ public void publishCoordinates() {
+
+ LocationEntity entity = (LocationEntity) widgetEntity;
+
+ if(entity.buttonPressed) {
+ Log.d(TAG, " Provider: " + provider + " Longitude: " + longitude + " Latitude: " + latitude + " Altitude " + altitude);
+ this.publishViewData(new LocationData(latitude, longitude, altitude, provider));
+ }
+ }
+
+}
diff --git a/app/src/main/res/layout/widget_detail_location.xml b/app/src/main/res/layout/widget_detail_location.xml
new file mode 100644
index 00000000..bddc5e46
--- /dev/null
+++ b/app/src/main/res/layout/widget_detail_location.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/widgets.xml b/app/src/main/res/values/widgets.xml
index 93278309..67b0fd68 100644
--- a/app/src/main/res/values/widgets.xml
+++ b/app/src/main/res/values/widgets.xml
@@ -12,6 +12,7 @@
- Logger
- RqtPlot
- Viz2D
+ - Location
@@ -99,6 +100,18 @@
You can zoom in and out by pinching 2 or more fingers together or apart.
+
+
+ The Location node is designed to publish the phone GPS and or network coordinates to ROS as a sensor_msgs/NavSatFix message.
+ The publisher can be started or stopped by pressing its button in the Viz tab.
+
+
+
+ - 0°
+ - 90°
+ - 180°
+ - 270°
+
@@ -145,4 +158,4 @@
2D View of multiple layers.
-
\ No newline at end of file
+
diff --git a/jcraft/src/main/AndroidManifest.xml b/jcraft/src/main/AndroidManifest.xml
index 1438840c..29a0cd81 100644
--- a/jcraft/src/main/AndroidManifest.xml
+++ b/jcraft/src/main/AndroidManifest.xml
@@ -1,2 +1,8 @@
+ package="com.jcraft" >
+
+
+
+
+
+