diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..6332594
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,14 @@
+Copyright (C) 2018 Norbert Truchsess norbert.truchsess@t-online.de
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f605938
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# send2Car
+Android App to send location to BMW car
+
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..e888524
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,46 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 28
+ defaultConfig {
+ applicationId "com.truchsess.send2car"
+ minSdkVersion 16
+ targetSdkVersion 28
+ versionCode 7
+ versionName "7"
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+
+ androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
+ exclude group: 'com.android.support', module: 'support-annotations'
+ })
+
+ implementation 'com.android.support:appcompat-v7:28.0.0'
+ implementation 'com.android.support:design:28.0.0'
+ implementation 'com.android.support.constraint:constraint-layout:1.1.3'
+ implementation 'com.android.support:preference-v7:28.0.0'
+
+ implementation 'com.google.code.gson:gson:2.8.2'
+
+ implementation 'com.squareup.retrofit2:retrofit:2.5.0'
+ implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
+
+ androidTestImplementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3'
+ androidTestImplementation group: 'xpp3', name: 'xpp3_min', version: '1.1.4c'
+ androidTestImplementation group: 'xmlpull', name: 'xmlpull', version: '1.1.3.1'
+ androidTestImplementation 'junit:junit:4.12'
+ androidTestImplementation 'xpp3:xpp3_min:1.1.4c'
+ androidTestImplementation 'xmlpull:xmlpull:1.1.3.1'
+ androidTestImplementation 'org.mockito:mockito-core:1.10.19'
+}
+
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..b0df89a
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,456 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/assets/nominatim.properties b/app/src/main/assets/nominatim.properties
new file mode 100644
index 0000000..a08f8e7
--- /dev/null
+++ b/app/src/main/assets/nominatim.properties
@@ -0,0 +1,168 @@
+airport Flughafen
+arts_centre Kunstzentrum
+atm Geldautomat
+auditorium Auditorium
+bank Bank
+bar Bar
+bench Sitzbank
+bicycle_parking Fahrradabstellplatz
+bicycle_rental Leihfahrräder
+brothel Bordell
+bureau_de_change Wechselstube
+bus_station Busbahnhof
+cafe Cafe
+car_rental Autovermietung
+car_wash Autowaschanlage
+casino Spielkasino
+cinema Kino
+clinic Krankenhaus
+club Klub
+college Hochschule
+community_centre Gemeindezentrum
+courthouse Gericht
+crematorium Krematorium
+dentist Zahnarzt
+doctors Ärzte
+dormitory Wohnheim
+drinking_water Trinkwasser
+driving_school Fahrschule
+embassy Botschaft
+emergency_phone Notruf
+fast_food Fast Food
+ferry_terminal Fähranlegestelle
+fire_hydrant Hydrant
+fire_station Feuerwache
+fountain Brunnen
+fuel Tankstelle
+grave_yard Friedhof
+hall Halle
+health_centre Gesundheitszentrum
+hospital Krankenhaus
+hotel Hotel
+hunting_stand Jagd Stand
+ice_cream Eiscafe
+kindergarten Kindergarten
+library Bibliotek
+market Markt
+marketplace Marktplatz
+nightclub Nachtclub
+nursery Kinderkrippe
+nursing_home Pflegeheim
+office Büro
+park Park
+parking Parkplatz
+pharmacy Apotheke
+place_of_worship Kirche
+police Polizei
+post_box Briefkasten
+post_office Postamt
+preschool Vorschule
+prison Gefängnis
+pub Kneipe
+public_Öffendliches Gebäude
+public_market Öffendlicher Markt
+reception_area Pforte
+restaurant Restaurant
+retirement_home Altenheim
+sauna Sauna
+school Schule
+shelter Schutzhütte
+shop Geschäft
+shopping Geschäft
+social_club Privater Club
+studio Studio
+supermarket Supermarkt
+taxi Taxistand
+telephone Telefon
+theatre Theater
+toilets Toiletten
+townhall Rathaus
+university Universität
+veterinary Tierarzt
+waste_basket Abfalleimer
+wifi Wireless LAN
+youth_centre Jugendzentrum
+apartments Wohnungen
+block Wohnblock
+bunker Bunker
+chapel Kapelle
+church Kirche
+commercial Gewerbegebäude
+dormitory Wohnheim
+entrance Eingang
+faculty Fakultät
+farm Bauernhof
+flats Wohnungen
+garage Werkstatt
+hospital Krankenhaus
+hotel Hotel
+house Haus
+industrial Industriegebäude
+office Büro
+public Öffendliches Gebäude
+residential Straße im Wohngebiet
+living_street Verkehrsberuhigter Bereich
+retail Einzelhandel
+school Schule
+shop Geschäft
+stadium Stadion
+store Geschäft
+terrace Terasse
+tower Turm
+train_station Bahnhof
+university Universität
+bridleway Reitweg
+bus_stop Bushaltestelle
+construction Baustelle
+cycleway Radweg
+distance_marker Entfernungsangabe
+emergency_access_point Anfahrtspunkt für Rettungsfahrzeuge
+footway Fußweg
+gate Gatter
+motorway Autobahn
+motorway_junction Abzweigung Autobahn
+path Pfad
+pedestrian Fußgängerzone
+platform Bahnsteig
+primary Hauptstraße
+primary_link Abzeigung Hauptstraße
+raceway Rennstrecke
+road Straße
+secondary Sekundärstraße
+secondary_link Abzweigung Sekundärstraße
+services Versorgungsstraße
+steps Treppe
+tertiary Nebenstraße
+track Wirtschaftsweg
+trail Pfad
+trunk Ausfallstraße
+trunk_link Abzweigung Ausfallstraße
+unsurfaced Unbefestigte Straße
+archaeological_site Archaelogische Stätte
+battlefield Schlachtfeld
+historisches Gebäude
+castle Burg
+church Kirche
+house historisches Gebäude
+icon Bildzeichen
+manor Landgut
+memorial Gedenkstätte
+mine Bergwerk
+monument Denkmal
+museum Museum
+ruins Ruinen
+tower Turm
+wayside_cross Kreuz
+wayside_shrine Schrein
+wreck Wrack
+cemetery Friedhof
+commercial Gewerbegebiet
+construction Baustelle
+farm Bauernhof
+farmland Feld
+farmyard Feld
+forest Wald
+grass Grass
+industrial Industriegebiet
+water Wasserfläche
+camp_site Campingplatz
\ No newline at end of file
diff --git a/app/src/main/java/com/truchsess/send2car/GeoFragment.java b/app/src/main/java/com/truchsess/send2car/GeoFragment.java
new file mode 100644
index 0000000..abf17d3
--- /dev/null
+++ b/app/src/main/java/com/truchsess/send2car/GeoFragment.java
@@ -0,0 +1,108 @@
+package com.truchsess.send2car;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.truchsess.send2car.geo.GeoUrl;
+
+/**********************************************************************************************
+ Copyright (C) 2018 Norbert Truchsess norbert.truchsess@t-online.de
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+ **********************************************************************************************/
+public class GeoFragment extends Fragment {
+
+ private EditText mEditTextLat;
+ private EditText mEditTextLon;
+ private EditText mEditTextDescription;
+
+ private Listener mListener;
+
+ public void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ public interface Listener {
+ void onLookupGeoDataClicked();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ final View view = inflater.inflate(R.layout.fragment_geo, container, false);
+
+ mEditTextLat = view.findViewById(R.id.editTextLat);
+ mEditTextLon = view.findViewById(R.id.editTextLon);
+ mEditTextDescription = view.findViewById(R.id.editTextDescription);
+
+ view.findViewById(R.id.buttonClearLat).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mEditTextLat.setText("");
+ }
+ });
+
+ view.findViewById(R.id.buttonClearLon).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mEditTextLon.setText("");
+ }
+ });
+
+ view.findViewById(R.id.buttonClearDescription).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mEditTextDescription.setText("");
+ }
+ });
+
+ view.findViewById(R.id.buttonLookupGeodata).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mListener != null) {
+ mListener.onLookupGeoDataClicked();
+ }
+ }
+ });
+
+ return view;
+ }
+
+ public void setGeoUrl(final GeoUrl geoUrl) {
+ mEditTextLat.setText(geoUrl != null && geoUrl.isValid() ? GeoUrl.degreeToString(geoUrl.getLat()) : " - ");
+ mEditTextLon.setText(geoUrl != null && geoUrl.isValid() ? GeoUrl.degreeToString(geoUrl.getLon()) : " - ");
+ mEditTextDescription.setText(geoUrl == null || geoUrl.getDescription() == null ? " - " : geoUrl.getDescription());
+ }
+
+ public GeoUrl getGeoUrl() {
+ double lat = Double.NaN;
+ double lon = Double.NaN;
+
+ try {
+ lat = Double.parseDouble(mEditTextLat.getText().toString());
+ lon = Double.parseDouble(mEditTextLon.getText().toString());
+ } catch (NumberFormatException nfe) {
+ }
+
+ return new GeoUrl(lat,lon,mEditTextDescription.getText().toString());
+ }
+}
diff --git a/app/src/main/java/com/truchsess/send2car/GetVinsFragment.java b/app/src/main/java/com/truchsess/send2car/GetVinsFragment.java
new file mode 100644
index 0000000..2d12cca
--- /dev/null
+++ b/app/src/main/java/com/truchsess/send2car/GetVinsFragment.java
@@ -0,0 +1,59 @@
+package com.truchsess.send2car;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+
+/**********************************************************************************************
+ Copyright (C) 2018 Norbert Truchsess norbert.truchsess@t-online.de
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+ **********************************************************************************************/
+public class GetVinsFragment extends Fragment {
+
+ private Button mButtonGetVins;
+ private Listener mListener;
+
+ public interface Listener {
+ void onGetVinsClicked();
+ }
+
+ public void setmListener(Listener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_vins, container, false);
+
+ mButtonGetVins = view.findViewById(R.id.buttonGetVins);
+
+ mButtonGetVins.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mListener != null) {
+ mListener.onGetVinsClicked();
+ }
+ }
+ });
+ return view;
+ }
+
+}
+
diff --git a/app/src/main/java/com/truchsess/send2car/ListPlacesFragment.java b/app/src/main/java/com/truchsess/send2car/ListPlacesFragment.java
new file mode 100644
index 0000000..9a254a1
--- /dev/null
+++ b/app/src/main/java/com/truchsess/send2car/ListPlacesFragment.java
@@ -0,0 +1,41 @@
+package com.truchsess.send2car;
+
+import android.support.v4.app.ListFragment;
+import android.view.View;
+import android.widget.ListView;
+
+/**********************************************************************************************
+ Copyright (C) 2018 Norbert Truchsess norbert.truchsess@t-online.de
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+ **********************************************************************************************/
+public class ListPlacesFragment extends ListFragment {
+
+ private Listener listener;
+
+ public interface Listener {
+ void onListItemClick(int position);
+ }
+
+ public void setListPlacesListener(Listener listener) {
+ this.listener = listener;
+ }
+
+ @Override
+ public void onListItemClick(ListView l, View v, int position, long id) {
+ if (listener != null) {
+ listener.onListItemClick(position);
+ }
+ }
+}
diff --git a/app/src/main/java/com/truchsess/send2car/MainActivity.java b/app/src/main/java/com/truchsess/send2car/MainActivity.java
new file mode 100644
index 0000000..4124946
--- /dev/null
+++ b/app/src/main/java/com/truchsess/send2car/MainActivity.java
@@ -0,0 +1,549 @@
+package com.truchsess.send2car;
+
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.database.DataSetObserver;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.preference.EditTextPreference;
+import android.support.v7.preference.PreferenceManager;
+import android.support.v7.widget.Toolbar;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import com.truchsess.send2car.cd.Token;
+import com.truchsess.send2car.cd.api.ErrorResponse;
+import com.truchsess.send2car.cd.entity.ServiceMessage;
+import com.truchsess.send2car.cd.entity.Vehicle;
+import com.truchsess.send2car.component.GetVehiclesController;
+import com.truchsess.send2car.component.SendServiceMessageController;
+import com.truchsess.send2car.geo.GeoUrl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.PropertyResourceBundle;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**********************************************************************************************
+ Copyright (C) 2018 Norbert Truchsess norbert.truchsess@t-online.de
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+ **********************************************************************************************/
+
+public class MainActivity extends AppCompatActivity implements View.OnClickListener {
+
+ private enum Story {
+ ePreferences,
+ eArguments,
+ eCurrentMessage
+ }
+
+ private Story currentStory;
+
+ private PreferencesFragment mPreferencesFragment;
+ private GetVinsFragment mGetVinsFragment;
+ private GeoFragment mGeoFragment;
+ private ListPlacesFragment mListPlacesFragment;
+ private PlaceFragment mPlaceFragment;
+ private StatusFragment mStatusFragment;
+ private ActionBar mActionBar;
+
+ private Token mToken;
+ private GetVehiclesController mGetVehiclesController;
+ private SendServiceMessageController mServiceMessageController;
+ private Set mServiceMessageDataSetObservers = new HashSet<>();
+ //need to store a strong reference to OnSharedPreferenceChangeListener. For Details see:
+ //https://developer.android.com/reference/android/content/SharedPreferences.html#registerOnSharedPreferenceChangeListener(android.content.SharedPreferences.OnSharedPreferenceChangeListener)
+ private SharedPreferences.OnSharedPreferenceChangeListener mOnSharedPreferenceChangeListener;
+
+ @Override
+ public void onAttachFragment(Fragment fragment) {
+ if (fragment instanceof PreferencesFragment) {
+ mPreferencesFragment = (PreferencesFragment) fragment;
+ return;
+ }
+ if (fragment instanceof GetVinsFragment) {
+ mGetVinsFragment = (GetVinsFragment) fragment;
+ return;
+ }
+ if (fragment instanceof GeoFragment) {
+ mGeoFragment = (GeoFragment) fragment;
+ return;
+ }
+ if (fragment instanceof ListPlacesFragment) {
+ mListPlacesFragment = (ListPlacesFragment) fragment;
+ return;
+ }
+ if (fragment instanceof PlaceFragment) {
+ mPlaceFragment = (PlaceFragment) fragment;
+ return;
+ }
+ if (fragment instanceof StatusFragment) {
+ mStatusFragment = (StatusFragment) fragment;
+ return;
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_main);
+ Toolbar toolbar = findViewById(R.id.toolbar_main);
+ setSupportActionBar(toolbar);
+ mActionBar = getSupportActionBar();
+
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+ mToken = new Token();
+ mGetVehiclesController = new GetVehiclesController();
+ mServiceMessageController = new SendServiceMessageController();
+
+ try {
+ final InputStream in = getBaseContext().getAssets().open(SendServiceMessageController.NOMINATIM_PROPERTIES);
+ if (in != null) {
+ Reader reader = new InputStreamReader(in,"UTF-8");
+ final PropertyResourceBundle nominatimCategories = new PropertyResourceBundle(reader);
+ mServiceMessageController.setCategories(nominatimCategories);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ mGetVinsFragment.setmListener(new GetVinsFragment.Listener() {
+ @Override
+ public void onGetVinsClicked() {
+ getVehicles();
+ }
+ });
+
+ mListPlacesFragment.setListAdapter(new ListAdapter() {
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return true;
+ }
+
+ @Override
+ public void registerDataSetObserver(DataSetObserver observer) {
+ mServiceMessageDataSetObservers.add(observer);
+ }
+
+ @Override
+ public void unregisterDataSetObserver(DataSetObserver observer) {
+ mServiceMessageDataSetObservers.remove(observer);
+ }
+
+ @Override
+ public int getCount() {
+ return mServiceMessageController.getNumServiceMessages();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mServiceMessageController.getServiceMessage(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return false;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final View view = convertView == null ?
+ getLayoutInflater().inflate(R.layout.list_place, null, true) :
+ convertView;
+ TextView textPlaceName = (TextView) view.findViewById(R.id.textListPlaceName);
+ textPlaceName.setText(((ServiceMessage)getItem(position)).getName());
+ return view;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return 0;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 1;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return mServiceMessageController.getNumServiceMessages() == 0;
+ }
+ });
+
+ mListPlacesFragment.setListPlacesListener(new ListPlacesFragment.Listener() {
+ @Override
+ public void onListItemClick(int position) {
+ mServiceMessageController.setServiceMessageIndex(position);
+ mStatusFragment.clear();
+ currentStory = Story.eCurrentMessage;
+ updateView();
+ }
+ });
+
+ mPlaceFragment.setPlaceFragmentListener(new PlaceFragment.Listener() {
+ @Override
+ public void onSend2CarClicked() {
+
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
+ final String vin = mPlaceFragment.getSelectedVin();
+
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putString(getString(R.string.key_preference_selected_vin),vin);
+ editor.commit();
+
+ if (vin == null) {
+ mStatusFragment.setStatus(getString(R.string.status_servicemessage),getString(R.string.status_novin));
+ updateView();
+ } else {
+ mStatusFragment.setStatus(getString(R.string.status_servicemessage),getString(R.string.status_authenticating));
+ updateView();
+
+ setTokenCredentials(sp);
+ mToken.checkToken(new Token.AuthenticationListener() {
+ @Override
+ public void onAuthentication() {
+ mStatusFragment.setStatus(getString(R.string.status_servicemessage),getString(R.string.status_sending));
+ updateView();
+
+ mServiceMessageController.setVin(vin);
+ mServiceMessageController.sendServiceMessage2Car(mToken, new SendServiceMessageController.ServiceMessageSendListener() {
+ @Override
+ public void onSent() {
+ mStatusFragment.setStatus(getString(R.string.status_servicemessage),getString(R.string.status_sent));
+ updateView();
+ }
+
+ @Override
+ public void onError(String error) {
+ mStatusFragment.setStatus(getString(R.string.status_servicemessage_error), error);
+ updateView();
+ }
+ });
+ }
+
+ @Override
+ public void onAuthenticationFailure(ErrorResponse errorResponse) {
+ mStatusFragment.setStatus(errorResponse.getError(), errorResponse.getError_description());
+ updateView();
+ }
+ });
+ }
+ }
+ });
+
+
+ mGeoFragment.setListener(new GeoFragment.Listener() {
+ @Override
+ public void onLookupGeoDataClicked() {
+ mServiceMessageController.setGeoUrl(mGeoFragment.getGeoUrl());
+ getGeoData();
+ }
+ });
+
+ final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
+ mOnSharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if (key.equals(getString(R.string.key_preference_vins))) {
+ setVinsFromPreferences(sharedPreferences);
+ }
+ }
+ };
+ sp.registerOnSharedPreferenceChangeListener(mOnSharedPreferenceChangeListener);
+
+ setVinsFromPreferences(sp);
+
+ currentStory = Story.eArguments;
+
+ // Get intent, action and MIME type
+ Intent intent = getIntent();
+ String action = intent.getAction();
+
+ if (Intent.ACTION_VIEW.equals(action)) {
+ final Uri uri = intent.getData();
+ final String scheme = uri.getScheme();
+ if (scheme.equals("geo") || scheme.equals("google.navigation")) {
+ mServiceMessageController.setGeoUrlFromUri(uri);
+ }
+ final GeoUrl geoUrl = mServiceMessageController.getGeoUrl();
+
+ if (mGeoFragment != null) {
+ mGeoFragment.setGeoUrl(geoUrl);
+ }
+
+ getGeoData();
+ }
+ }
+
+ private void setVinsFromPreferences(SharedPreferences sharedPreferences) {
+
+ final String keyPreferenceVins = getString(R.string.key_preference_vins);
+ final String vinsPreference = sharedPreferences.getString(keyPreferenceVins,"");
+
+ final EditTextPreference editTextPreferenceVins = (EditTextPreference) mPreferencesFragment.findPreference(keyPreferenceVins);
+ if (editTextPreferenceVins != null) {
+ final String editTextPreferenceVinsText = editTextPreferenceVins.getText();
+ if (editTextPreferenceVins != null && !editTextPreferenceVins.equals(vinsPreference)) {
+ editTextPreferenceVins.setText(vinsPreference);
+ }
+ }
+
+ final Map vins = new HashMap();
+ final Pattern pattern = Pattern.compile("([A-Z0-9]+)(\\(([^\\n]*)\\)){0,1}");
+ Matcher matcher = pattern.matcher(vinsPreference);
+ while(matcher.find()) {
+ final int num = matcher.groupCount();
+ final String vin = matcher.group(1);
+ final String description = matcher.group(3);
+ vins.put(vin,description == null ? vin : description);
+ }
+ mPlaceFragment.setVins(vins);
+ mPlaceFragment.setCurrentVin(sharedPreferences.getString(getString(R.string.key_preference_selected_vin),""));
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.menu, menu);
+ return true;
+ }
+
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch(item.getItemId())
+ {
+ case R.id.help:
+ Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.url_help)));
+ startActivity(browserIntent);
+ break;
+ case R.id.preferences:
+ mStatusFragment.clear();
+ currentStory = Story.ePreferences;
+ updateView();
+ break;
+ case android.R.id.home:
+ mStatusFragment.clear();
+ currentStory = Story.eArguments;
+ updateView();
+ break;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ return true;
+ }
+
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ }
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume(); // Always call the superclass method first
+
+ updateView(true);
+ }
+
+ private void getGeoData() {
+
+ mStatusFragment.setStatus(getString(R.string.status_lookup_geo),getString(R.string.status_loading));
+ updateView();
+
+ mServiceMessageController.lookupGeodata(new SendServiceMessageController.ServiceMessageUpdateListener() {
+ @Override
+ public void onUpdate() {
+ for (DataSetObserver observer : mServiceMessageDataSetObservers) {
+ observer.onChanged();
+ }
+ mStatusFragment.setStatus(getString(R.string.status_lookup_geo),getString(R.string.status_success));
+ updateView();
+ }
+
+ @Override
+ public void onError(String error) {
+ for (DataSetObserver observer : mServiceMessageDataSetObservers) {
+ observer.onInvalidated();
+ }
+ mStatusFragment.setStatus(getString(R.string.status_lookup_geo_error), error);
+ updateView();
+ }
+ });
+ }
+
+ private void getVehicles() {
+
+ mStatusFragment.setStatus(getString(R.string.status_get_vehicles),getString(R.string.status_loading));
+ updateView();
+
+ setTokenCredentials(PreferenceManager.getDefaultSharedPreferences(getBaseContext()));
+
+ mToken.checkToken(new Token.AuthenticationListener() {
+ @Override
+ public void onAuthentication() {
+ mGetVehiclesController.getVehiclesResponse(mToken, new GetVehiclesController.GetVehiclesListener() {
+ @Override
+ public void onUpdate() {
+ List vins = new ArrayList<>();
+ int num = mGetVehiclesController.getNumVehicles();
+ boolean isNotFirst = false;
+ StringBuilder vinBuilder = new StringBuilder();
+ for (int i = 0; i < num; i++) {
+ Vehicle vehicle = mGetVehiclesController.getVehicle(i);
+ if (isNotFirst) {
+ vinBuilder.append('\n');
+ } else {
+ isNotFirst = true;
+ }
+ vinBuilder.append(vehicle.getVin())
+ .append("(")
+ .append(vehicle.getModel())
+ .append(")");
+ }
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putString(getString(R.string.key_preference_vins), vinBuilder.toString());
+ editor.commit();
+ mStatusFragment.setStatus(getString(R.string.status_get_vehicles),getString(R.string.status_success));
+ updateView();
+ }
+
+ @Override
+ public void onError(String error) {
+ mStatusFragment.setStatus(getString(R.string.status_get_vehicles_error), error);
+ updateView();
+ }
+ });
+ }
+
+ @Override
+ public void onAuthenticationFailure(ErrorResponse errorResponse) {
+ mStatusFragment.setStatus(errorResponse.getError(),errorResponse.getError_description());
+ updateView();
+ }
+ });
+ }
+
+ private void setTokenCredentials(SharedPreferences sharedPreferences) {
+ final String api_key = sharedPreferences.getString(getString(R.string.key_preference_apikey), "");
+ final String api_secret = sharedPreferences.getString(getString(R.string.key_preference_apisecret), "");
+ final String username = sharedPreferences.getString(getString(R.string.key_preference_username), "");
+ final String password = sharedPreferences.getString(getString(R.string.key_preference_password), "");
+ mToken.setCredentials(username, password, api_key, api_secret);
+ }
+
+ private void updateView(boolean force) {
+
+ FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+
+ switch (currentStory) {
+ case ePreferences:
+ mActionBar.setDisplayHomeAsUpEnabled(true);
+ showFragment(ft,mPreferencesFragment,force);
+ showFragment(ft,mGetVinsFragment,force);
+ hideFragment(ft,mGeoFragment,force);
+ hideFragment(ft,mListPlacesFragment,force);
+ hideFragment(ft,mPlaceFragment,force);
+ break;
+
+ case eArguments:
+ mActionBar.setDisplayHomeAsUpEnabled(false);
+ hideFragment(ft,mPreferencesFragment,force);
+ hideFragment(ft,mGetVinsFragment,force);
+ showFragment(ft,mGeoFragment,force);
+ showFragment(ft,mListPlacesFragment,force);
+ hideFragment(ft,mPlaceFragment,force);
+ break;
+
+ case eCurrentMessage:
+ mActionBar.setDisplayHomeAsUpEnabled(true);
+ hideFragment(ft,mPreferencesFragment,force);
+ hideFragment(ft,mGetVinsFragment,force);
+ hideFragment(ft,mGeoFragment,force);
+ hideFragment(ft,mListPlacesFragment,force);
+ showFragment(ft,mPlaceFragment,force);
+
+ final ServiceMessage serviceMessage = mServiceMessageController.getServiceMessage();
+ mPlaceFragment.setServiceMessage(serviceMessage);
+ break;
+ }
+ if (mStatusFragment.isSet()) {
+ showFragment(ft,mStatusFragment,force);
+ } else {
+ hideFragment(ft,mStatusFragment,force);
+ }
+ ft.commit();
+ }
+
+ private void updateView() {
+ updateView(false);
+ }
+
+ private void hideFragment(final FragmentTransaction ft, final Fragment f, final boolean force) {
+ if (f != null && (f.isVisible() || force)) {
+ ft.hide(f);
+ }
+ }
+
+ private void showFragment(final FragmentTransaction ft, final Fragment f, final boolean force) {
+ if (f != null && (f.isHidden() || force)) {
+ ft.show(f);
+ }
+ }
+}
diff --git a/app/src/main/java/com/truchsess/send2car/PlaceFragment.java b/app/src/main/java/com/truchsess/send2car/PlaceFragment.java
new file mode 100644
index 0000000..02a1af2
--- /dev/null
+++ b/app/src/main/java/com/truchsess/send2car/PlaceFragment.java
@@ -0,0 +1,207 @@
+package com.truchsess.send2car;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.TextView;
+
+import com.truchsess.send2car.cd.entity.ServiceMessage;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**********************************************************************************************
+ Copyright (C) 2018 Norbert Truchsess norbert.truchsess@t-online.de
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+ **********************************************************************************************/
+public class PlaceFragment extends Fragment {
+
+ private ServiceMessage mServiceMessage;
+ private TextView mTextViewPoiName;
+ private TextView mTextViewPoiLat;
+ private TextView mTextViewPoiLon;
+ private TextView mTextViewPoiCity;
+ private TextView mTextViewPoiStreet;
+ private TextView mTextViewPoiNumber;
+ private TextView mTextViewPoiPostalCode;
+
+ private EditText mEditTextPhone;
+ private EditText mEditTextSubject;
+ private EditText mEditTextMessage;
+
+ private RadioGroup mRadioGroupVins;
+
+ private Button mButtonSend2Car;
+
+ private Listener mListener;
+
+ private Map mVins = new HashMap<>();
+ private String mCurrentVin;
+
+ public interface Listener {
+ void onSend2CarClicked();
+ }
+
+ public void setPlaceFragmentListener(Listener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_place, container, false);
+
+ mTextViewPoiName = view.findViewById(R.id.textViewPoiName);
+ mTextViewPoiLat = view.findViewById(R.id.textViewPoiLat);
+ mTextViewPoiLon = view.findViewById(R.id.textViewPoiLon);
+ mTextViewPoiCity = view.findViewById(R.id.textViewPoiCity);
+ mTextViewPoiPostalCode = view.findViewById(R.id.textViewPoiPostalCode);
+ mTextViewPoiStreet = view.findViewById(R.id.textViewPoiStreet);
+ mTextViewPoiNumber = view.findViewById(R.id.textViewPoiNumber);
+
+ mEditTextPhone = view.findViewById(R.id.editTextPoiPhone);
+ mEditTextSubject = view.findViewById(R.id.editTextSubject);
+ mEditTextMessage = view.findViewById(R.id.editTextMessage);
+
+ mRadioGroupVins = view.findViewById(R.id.radioGroupVins);
+ mRadioGroupVins.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(RadioGroup group, int checkedId) {
+ final View view = group.findViewById(checkedId);
+ mCurrentVin = view == null ? null : (String) view.getTag();
+ }
+ });
+ mButtonSend2Car = view.findViewById(R.id.buttonSend2Car);
+
+ mButtonSend2Car.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ mServiceMessage.setPhone1(mEditTextPhone.getText().toString());
+ mServiceMessage.setSubject(mEditTextSubject.getText().toString());
+ mServiceMessage.setMessage(mEditTextMessage.getText().toString());
+
+ if (mListener != null) {
+ mListener.onSend2CarClicked();
+ }
+ }
+ });
+ return view;
+ }
+
+ public void setServiceMessage(final ServiceMessage serviceMessage) {
+ mServiceMessage = serviceMessage;
+ updateView();
+ }
+
+ public void setVins(Map vins) {
+ mVins.clear();
+ mVins.putAll(vins);
+ updateView();
+ }
+
+ public void setCurrentVin(String vin) {
+ mCurrentVin = vin;
+ updateView();
+ }
+
+ public String getSelectedVin() {
+ final int id = mRadioGroupVins.getCheckedRadioButtonId();
+ if (id != -1) {
+ final View view = mRadioGroupVins.findViewById(id);
+ if (view != null) {
+ return (String) view.getTag();
+ }
+ }
+ return null;
+ }
+
+ public void updateView() {
+ if (mServiceMessage != null) {
+ mTextViewPoiName.setText(mServiceMessage.getName());
+ mTextViewPoiLat.setText(mServiceMessage.getLat());
+ mTextViewPoiLon.setText(mServiceMessage.getLng());
+ mTextViewPoiCity.setText(mServiceMessage.getCity());
+ mTextViewPoiPostalCode.setText(mServiceMessage.getZip());
+ mTextViewPoiStreet.setText(mServiceMessage.getStreet());
+ mTextViewPoiNumber.setText(mServiceMessage.getNumber());
+ mEditTextPhone.setText(mServiceMessage.getPhone1());
+ mEditTextSubject.setText(mServiceMessage.getSubject());
+ mEditTextMessage.setText(mServiceMessage.getMessage());
+
+ final int numButtons = mRadioGroupVins.getChildCount();
+ final Set viewsToRemove = new HashSet<>();
+ final Set