diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2453eaa..deb1a5a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -25,6 +25,7 @@ + diff --git a/app/src/main/java/fr/chteufleur/mytrackingdog/MainActivity.java b/app/src/main/java/fr/chteufleur/mytrackingdog/MainActivity.java index e84a1d2..68e0419 100644 --- a/app/src/main/java/fr/chteufleur/mytrackingdog/MainActivity.java +++ b/app/src/main/java/fr/chteufleur/mytrackingdog/MainActivity.java @@ -3,6 +3,7 @@ package fr.chteufleur.mytrackingdog; import android.Manifest; import android.annotation.SuppressLint; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; @@ -22,8 +23,10 @@ import android.support.design.widget.NavigationView; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBar; +import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; +import android.text.InputType; import android.util.Log; import android.view.Menu; import android.view.MenuItem; @@ -32,11 +35,13 @@ import android.view.Surface; import android.view.View; import android.view.WindowManager; import android.widget.Button; +import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.roster.RosterEntry; import org.jxmpp.stringprep.XmppStringprepException; import org.osmdroid.api.IMapController; import org.osmdroid.config.Configuration; @@ -59,6 +64,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Observable; import java.util.Observer; +import java.util.Set; import fr.chteufleur.mytrackingdog.models.Notification; import fr.chteufleur.mytrackingdog.models.beans.MyLocation; @@ -75,6 +81,7 @@ public class MainActivity extends AppCompatActivity implements IOrientationConsu private static final int ACTIVITY_REQUEST_PICK_FILE = 1; private static final int ACTIVITY_QR_CODE_GENERATOR = 2; private static final int ACTIVITY_QR_CODE_READER = 3; + private static final int ACTIVITY_XMPP_FRIENDS_LIST = 4; private ServiceTrackingDog serviceTrackingDog = null; public static String appName = ""; @@ -403,6 +410,10 @@ public class MainActivity extends AppCompatActivity implements IOrientationConsu sendGpxFileByXmpp(); mDrawerLayout.closeDrawers(); return true; + } else if (id == R.id.action_xmpp_friends_list) { + startActivityXmppFriendsList(); + mDrawerLayout.closeDrawers(); + return true; } return super.onOptionsItemSelected(item); @@ -485,6 +496,7 @@ public class MainActivity extends AppCompatActivity implements IOrientationConsu navigationView.getMenu().findItem(R.id.action_qr_code_reader).setEnabled(newStat); navigationView.getMenu().findItem(R.id.action_send_gpx_trail_by_xmpp).setEnabled(newStat && serviceTrackingDog.getLastExportedTrailFile() != null); navigationView.getMenu().findItem(R.id.action_active_xmpp_real_time_mode).setEnabled(newStat); + navigationView.getMenu().findItem(R.id.action_xmpp_friends_list).setEnabled(newStat); item.setChecked(newStat); item.setIcon(getResources().getDrawable(newStat ? R.drawable.ic_check_box_checked : R.drawable.ic_check_box_unchecked)); } @@ -509,6 +521,24 @@ public class MainActivity extends AppCompatActivity implements IOrientationConsu startActivityForResult(intent, ACTIVITY_QR_CODE_READER); } + private void startActivityXmppFriendsList() { + Intent intent = new Intent(this, XmppFriendsListActivity.class); + Set rosterEntries = serviceTrackingDog.getRosterEntries(); + String[] friendsListArray = new String[rosterEntries.size()]; + int i = 0; + for (RosterEntry rosterEntry: rosterEntries) { + String name = rosterEntry.getName(); + if (name != null) { + friendsListArray[i] = name; + } else { + friendsListArray[i] = rosterEntry.getJid().asBareJid().toString(); + } + i++; + } + intent.putExtra(XmppFriendsListActivity.EXTRA_FRIENDS_LIST, friendsListArray); + startActivityForResult(intent, ACTIVITY_XMPP_FRIENDS_LIST); + } + private void sendGpxFileByXmpp() { File trailFile = serviceTrackingDog.getLastExportedTrailFile(); if (trailFile != null) { @@ -566,9 +596,47 @@ public class MainActivity extends AppCompatActivity implements IOrientationConsu case ACTIVITY_QR_CODE_READER: if (data.hasExtra(QRCodeReaderActivity.EXTRA_SCANNED_TEXT)) { - String text = data.getStringExtra(QRCodeReaderActivity.EXTRA_SCANNED_TEXT); + final String text = data.getStringExtra(QRCodeReaderActivity.EXTRA_SCANNED_TEXT); try { serviceTrackingDog.setOtherJid(text); + + boolean isInRoster = serviceTrackingDog.isInRoster(text); + if (!isInRoster) { + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); + dialogBuilder.setTitle("Ajout à la liste d’amis"); + dialogBuilder.setMessage("Voulez-vous ajouter cette personne à votre liste d’amis ?"); + final EditText inputName = new EditText(this); + inputName.setHint("Donner un nom"); + inputName.setInputType(InputType.TYPE_CLASS_TEXT); + dialogBuilder.setView(inputName); + dialogBuilder.setPositiveButton("Oui", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + try { + serviceTrackingDog.sendSubscription(text); + + final String _name = inputName.getText().toString(); + if (!_name.equals("")) { + new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + } + serviceTrackingDog.setJidName(text, _name); + } + }).start(); + } + } catch (SmackException.NotConnectedException | InterruptedException | XmppStringprepException e) { + e.printStackTrace(); + } + } + }); + dialogBuilder.setNegativeButton("Non", null); + AlertDialog dialog = dialogBuilder.create(); + dialog.show(); + } Log.i(TAG, "TEXT: "+text); } catch (SmackException.NotConnectedException | InterruptedException | XmppStringprepException e) { Log.e(TAG, "Fail send presence", e); @@ -576,6 +644,25 @@ public class MainActivity extends AppCompatActivity implements IOrientationConsu } } break; + + case ACTIVITY_XMPP_FRIENDS_LIST: + if (data.hasExtra(XmppFriendsListActivity.EXTRA_SELECTED_FRIEND)) { + String selectedFriend = data.getStringExtra(XmppFriendsListActivity.EXTRA_SELECTED_FRIEND); + String friendJid; + if (selectedFriend.contains("@")) { + friendJid = selectedFriend; + } else { + friendJid = serviceTrackingDog.getFullJidByName(selectedFriend); + } + try { + serviceTrackingDog.setOtherJid(friendJid); + Log.i(TAG, "Connexion to: "+selectedFriend); + } catch (SmackException.NotConnectedException | InterruptedException | XmppStringprepException e) { + Log.e(TAG, "Fail send presence", e); + Toast.makeText(ctx, "Echec connexion avec le matériel", Toast.LENGTH_LONG).show(); + } + } + break; } } } @@ -862,6 +949,39 @@ public class MainActivity extends AppCompatActivity implements IOrientationConsu navigationView.getMenu().findItem(R.id.action_active_xmpp_real_time_mode).setChecked(serviceTrackingDog.isXmppRealTimeMode()); } }); + } else if (notification.isAction(ServiceXmpp.NOTIF_NEW_PRESENCE_RECEIVED)) { + final String _jid = (String) notification.getExtra(ServiceXmpp.NOTIF_NEW_PRESENCE_RECEIVED_VALUE_JID); + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(ctx, "Connexion avec "+_jid, Toast.LENGTH_LONG).show(); + } + }); + } else if (notification.isAction(ServiceXmpp.NOTIF_NEW_PRESENCE_SUBSCRIPTION)) { + final String _jid = (String) notification.getExtra(ServiceXmpp.NOTIF_NEW_PRESENCE_RECEIVED_VALUE_JID); + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Demande d'ami"); + builder.setMessage("Voulez-vous l'ajouter dans votre liste d'ami ?"); + builder.setPositiveButton("Oui", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + try { + serviceTrackingDog.sendAcceptSubscription(_jid); + serviceTrackingDog.sendSubscription(_jid); + } catch (SmackException.NotConnectedException | XmppStringprepException | InterruptedException e) { + e.printStackTrace(); + } + } + }); + builder.setNegativeButton("Non", null); + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(ctx, "Demande d'ami", Toast.LENGTH_LONG).show(); + AlertDialog dialog = builder.create(); + dialog.show(); + } + }); } } diff --git a/app/src/main/java/fr/chteufleur/mytrackingdog/XmppFriendsListActivity.java b/app/src/main/java/fr/chteufleur/mytrackingdog/XmppFriendsListActivity.java new file mode 100644 index 0000000..8dbc74a --- /dev/null +++ b/app/src/main/java/fr/chteufleur/mytrackingdog/XmppFriendsListActivity.java @@ -0,0 +1,46 @@ +package fr.chteufleur.mytrackingdog; + +import android.app.ListActivity; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.ListAdapter; +import android.widget.ListView; + +import java.util.Arrays; +import java.util.List; + +public class XmppFriendsListActivity extends ListActivity { + + public static final String EXTRA_FRIENDS_LIST = "extra_friends_list"; + public static final String EXTRA_SELECTED_FRIEND = "extra_selected_friend"; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.xmpp_friends_list); + + Intent intent = getIntent(); + String[] friendsListArray = intent.getStringArrayExtra(EXTRA_FRIENDS_LIST); + List list = Arrays.asList(friendsListArray); // Doesn't work + ArrayAdapter listDataAdapter = new ArrayAdapter<>(this, R.layout.xmpp_friend_info, R.id.xmpp_friend_info, list); + this.setListAdapter(listDataAdapter); + } + + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + ListAdapter listAdapter = l.getAdapter(); + // Get user selected item object. + String selectedFriend = (String) listAdapter.getItem(position); + + Intent extra = new Intent(); + extra.putExtra(EXTRA_SELECTED_FRIEND, selectedFriend); + setResult(RESULT_OK, extra); + finish(); + + super.onListItemClick(l, v, position, id); + } +} diff --git a/app/src/main/java/fr/chteufleur/mytrackingdog/services/ServiceTrackingDog.java b/app/src/main/java/fr/chteufleur/mytrackingdog/services/ServiceTrackingDog.java index 53235d2..52b627c 100644 --- a/app/src/main/java/fr/chteufleur/mytrackingdog/services/ServiceTrackingDog.java +++ b/app/src/main/java/fr/chteufleur/mytrackingdog/services/ServiceTrackingDog.java @@ -11,6 +11,7 @@ import android.os.Vibrator; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.roster.RosterEntry; import org.jxmpp.stringprep.XmppStringprepException; import org.xmlpull.v1.XmlPullParserException; @@ -23,6 +24,8 @@ import java.util.Date; import java.util.List; import java.util.Observable; import java.util.Observer; +import java.util.Set; +import java.util.TreeSet; import fr.chteufleur.mytrackingdog.QRCodeGeneratorActivity; import fr.chteufleur.mytrackingdog.models.ExportGpx; @@ -394,6 +397,44 @@ public class ServiceTrackingDog implements Observer { } return ret; } + public Set getRosterEntries() { + Set ret; + if (serviceXmpp != null) { + ret = serviceXmpp.getRosterEntries(); + } else { + ret = new TreeSet<>(); + } + return ret; + } + public boolean isInRoster(String jid) { + boolean ret = false; + if (serviceXmpp != null) { + ret = serviceXmpp.isInRoster(jid); + } + return ret; + } + public String getFullJidByName(String name) { + String ret = ""; + if (serviceXmpp != null) { + ret = serviceXmpp.getFullJidByName(name); + } + return ret; + } + public void setJidName(String jid, String name) { + if (serviceXmpp != null) { + serviceXmpp.setJidName(jid, name); + } + } + public void sendSubscription(String fullJid) throws SmackException.NotConnectedException, XmppStringprepException, InterruptedException { + if (serviceXmpp != null) { + serviceXmpp.sendPresenceSubscribe(fullJid); + } + } + public void sendAcceptSubscription(String fullJid) throws SmackException.NotConnectedException, XmppStringprepException, InterruptedException { + if (serviceXmpp != null) { + serviceXmpp.sendPresenceSubscribed(fullJid); + } + } // public void sendXmppCommandStartTrail() throws XmppStringprepException { if (serviceXmpp != null) { diff --git a/app/src/main/java/fr/chteufleur/mytrackingdog/services/ServiceXmpp.java b/app/src/main/java/fr/chteufleur/mytrackingdog/services/ServiceXmpp.java index 58749ec..7019805 100644 --- a/app/src/main/java/fr/chteufleur/mytrackingdog/services/ServiceXmpp.java +++ b/app/src/main/java/fr/chteufleur/mytrackingdog/services/ServiceXmpp.java @@ -12,11 +12,16 @@ import org.jivesoftware.smack.AbstractXMPPConnection; import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.ConnectionListener; import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.StanzaListener; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.PresenceTypeFilter; +import org.jivesoftware.smack.filter.StanzaFilter; import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.roster.PresenceEventListener; import org.jivesoftware.smack.roster.Roster; +import org.jivesoftware.smack.roster.RosterEntry; import org.jivesoftware.smack.tcp.XMPPTCPConnection; import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration; import org.jivesoftware.smackx.commands.AdHocCommandManager; @@ -43,7 +48,8 @@ import java.net.Inet4Address; import java.net.UnknownHostException; import java.util.LinkedList; import java.util.Observable; -import java.util.Random; +import java.util.Set; +import java.util.TreeSet; import fr.chteufleur.mytrackingdog.QRCodeGeneratorActivity; import fr.chteufleur.mytrackingdog.models.Notification; @@ -61,7 +67,7 @@ import fr.chteufleur.mytrackingdog.models.xmpp.commands.send.SendStartTrailComma import fr.chteufleur.mytrackingdog.models.xmpp.commands.send.SendStopTrailCommand; import fr.chteufleur.mytrackingdog.models.xmpp.commands.send.SendTrailLocationCommand; -public class ServiceXmpp extends Observable implements Runnable, ConnectionListener, PresenceEventListener, FileTransferListener { +public class ServiceXmpp extends Observable implements Runnable, ConnectionListener, StanzaListener, PresenceEventListener, FileTransferListener { public static final String TAG = ServiceXmpp.class.getName(); @@ -70,6 +76,7 @@ public class ServiceXmpp extends Observable implements Runnable, ConnectionListe public static final String NOTIF_START_TRAIL = ServiceXmpp.class.getName()+".starttrail"; public static final String NOTIF_STOP_TRAIL = ServiceXmpp.class.getName()+".stoptrail"; public static final String NOTIF_NEW_PRESENCE_RECEIVED = ServiceXmpp.class.getName()+".newpresencereceived"; + public static final String NOTIF_NEW_PRESENCE_SUBSCRIPTION = ServiceXmpp.class.getName()+".newpresencesubscriptionreceived"; public static final String NOTIF_RECEIVING_FILE = ServiceXmpp.class.getName()+".receivingfile"; public static final String NOTIF_RECEIVING_FILE_COMPLETTED = NOTIF_RECEIVING_FILE+".completed"; public static final String NOTIF_RECEIVING_FILE_FAIL = NOTIF_RECEIVING_FILE+".receivingfile"; @@ -91,6 +98,7 @@ public class ServiceXmpp extends Observable implements Runnable, ConnectionListe private static final String XMPP_DOMAIN_SERVER = "dog.xmpp.kingpenguin.tk"; // private static final String XMPP_DOMAIN_SERVER = "kingpenguin.tk"; private static final int XMPP_PORT = 5222; + public static final String XMPP_RESOURCE = "MyTrackingDog"; // private final SharedPreferences preferences; @@ -111,11 +119,14 @@ public class ServiceXmpp extends Observable implements Runnable, ConnectionListe private FileTransferManager fileManager; private AccountManager accountManager; private AbstractXMPPConnection connection; + private Roster roster; private String jid; private String otherJid; private final ServiceXmpp thsi; private boolean run = false; + private StanzaFilter subscribefilter = PresenceTypeFilter.SUBSCRIBE; + private boolean isEnable = false; private boolean isRealTimeMode = false; @@ -170,6 +181,7 @@ public class ServiceXmpp extends Observable implements Runnable, ConnectionListe boolean isConnected; connection = new XMPPTCPConnection(configuration); connection.addConnectionListener(this); + connection.addAsyncStanzaListener(this, subscribefilter); commandManager = AdHocCommandManager.getAddHocCommandsManager(connection); fileManager = FileTransferManager.getInstanceFor(connection); connection.connect(); @@ -254,7 +266,7 @@ public class ServiceXmpp extends Observable implements Runnable, ConnectionListe } try { - connection.login(prefXmppUser, prefXmppPassword, Resourcepart.from("MyTrackingDog")); + connection.login(prefXmppUser, prefXmppPassword, Resourcepart.from(XMPP_RESOURCE)); } catch (XmppStringprepException e) { e.printStackTrace(); } catch (InterruptedException | IOException | SmackException | XMPPException e) { @@ -263,7 +275,9 @@ public class ServiceXmpp extends Observable implements Runnable, ConnectionListe registerCommands(); fileManager.addFileTransferListener(this); - Roster roster = Roster.getInstanceFor(connection); + + roster = Roster.getInstanceFor(connection); + roster.setSubscriptionMode(Roster.SubscriptionMode.manual); roster.addPresenceEventListener(this); } @@ -295,7 +309,6 @@ public class ServiceXmpp extends Observable implements Runnable, ConnectionListe } }; new Thread(r).start(); - } @Override @@ -338,16 +351,54 @@ public class ServiceXmpp extends Observable implements Runnable, ConnectionListe // public void sendPresenceAvailable() throws SmackException.NotConnectedException, InterruptedException, XmppStringprepException { if (otherJid != null) { - sendPresence(JidCreate.fullFrom(otherJid), Presence.Type.available); + Jid jid; + if (otherJid.contains("/")) { + jid = JidCreate.fullFrom(otherJid); + } else { + jid = JidCreate.bareFrom(otherJid); + } + sendPresence(jid, Presence.Type.available); } } public void sendPresenceAvailable(String to) throws SmackException.NotConnectedException, InterruptedException, XmppStringprepException { - sendPresence(JidCreate.fullFrom(to), Presence.Type.available); + Jid jid; + if (to.contains("/")) { + jid = JidCreate.fullFrom(to); + } else { + jid = JidCreate.bareFrom(to); + } + sendPresence(jid, Presence.Type.available); } public void sendPresenceUnavailable(String to) throws SmackException.NotConnectedException, InterruptedException, XmppStringprepException { - sendPresence(JidCreate.fullFrom(to), Presence.Type.unavailable); + Jid jid; + if (to.contains("/")) { + jid = JidCreate.fullFrom(to); + } else { + jid = JidCreate.bareFrom(to); + } + sendPresence(jid, Presence.Type.unavailable); + } + + public void sendPresenceSubscribe(String to) throws XmppStringprepException, SmackException.NotConnectedException, InterruptedException { + Jid jid; + if (to.contains("/")) { + jid = JidCreate.fullFrom(to); + } else { + jid = JidCreate.bareFrom(to); + } + sendPresence(jid, Presence.Type.subscribe); + } + + public void sendPresenceSubscribed(String to) throws XmppStringprepException, SmackException.NotConnectedException, InterruptedException { + Jid jid; + if (to.contains("/")) { + jid = JidCreate.fullFrom(to); + } else { + jid = JidCreate.bareFrom(to); + } + sendPresence(jid, Presence.Type.subscribed); } private void sendPresence(Jid to, Presence.Type type) throws SmackException.NotConnectedException, InterruptedException { @@ -407,6 +458,28 @@ public class ServiceXmpp extends Observable implements Runnable, ConnectionListe @Override public void presenceUnsubscribed(BareJid address, Presence unsubscribedPresence) { } + + @Override + public void processStanza(Stanza packet) throws SmackException.NotConnectedException, InterruptedException { + if (packet instanceof Presence) { + Presence presence = (Presence) packet; + String from = presence.getFrom().toString(); + Presence.Type type = presence.getType(); + + if (type == Presence.Type.subscribe) { + if (isInRoster(from)) { + try { + sendPresenceSubscribed(from); + } catch (XmppStringprepException e) { + e.printStackTrace(); + } + } else { + setChanged(); + notifyObservers(new Notification(NOTIF_NEW_PRESENCE_SUBSCRIPTION).addExtra(NOTIF_NEW_PRESENCE_RECEIVED_VALUE_JID, from)); + } + } + } + } // // @@ -595,6 +668,60 @@ public class ServiceXmpp extends Observable implements Runnable, ConnectionListe } // + // + public Set getRosterEntries() { + Set ret; + if (roster != null) { + ret = roster.getEntries(); + } else { + ret = new TreeSet<>(); + } + return ret; + } + + public boolean isInRoster(String jid) { + boolean ret = false; + if (jid != null) { + for (RosterEntry entry: roster.getEntries()) { + if (jid.startsWith(entry.getJid().asBareJid().toString())) { + ret = true; + break; + } + } + } + return ret; + } + + public String getFullJidByName(String name) { + String ret = ""; + if (name != null) { + for (RosterEntry entry: roster.getEntries()) { + String _name = entry.getName(); + if (_name != null && _name.equals(name)) { + ret = entry.getJid().asBareJid().toString()+"/"+ServiceXmpp.XMPP_RESOURCE; + break; + } + } + } + return ret; + } + + public void setJidName(String jid, String name) { + if (jid != null && name != null) { + for (RosterEntry entry: roster.getEntries()) { + if (jid.startsWith(entry.getJid().asBareJid().toString())) { + try { + entry.setName(name); + } catch (SmackException.NotConnectedException | SmackException.NoResponseException | XMPPException.XMPPErrorException | InterruptedException e) { + e.printStackTrace(); + } + break; + } + } + } + } + // + public void setOtherJid(String otherJid) { this.otherJid = otherJid; } diff --git a/app/src/main/res/layout/xmpp_friend_info.xml b/app/src/main/res/layout/xmpp_friend_info.xml new file mode 100644 index 0000000..c65bb2d --- /dev/null +++ b/app/src/main/res/layout/xmpp_friend_info.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/xmpp_friends_list.xml b/app/src/main/res/layout/xmpp_friends_list.xml new file mode 100644 index 0000000..1067094 --- /dev/null +++ b/app/src/main/res/layout/xmpp_friends_list.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/drawer_view.xml b/app/src/main/res/menu/drawer_view.xml index d0c0992..1bd8f97 100644 --- a/app/src/main/res/menu/drawer_view.xml +++ b/app/src/main/res/menu/drawer_view.xml @@ -46,6 +46,9 @@ android:id="@+id/action_send_gpx_trail_by_xmpp" android:title="@string/action_send_gpx_trail_by_xmpp" android:icon="@drawable/ic_share" /> + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9b99699..3f18fb2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -20,4 +20,6 @@ Activer social Activer mode temps réel Envoyer trace du traceur + Amis + List d’amis