diff --git a/mobile/com.sap.sailing.android.tracking.app.test-test/src/com/sap/sailing/android/tracking/app/test/CheckinDataTest.java b/mobile/com.sap.sailing.android.tracking.app.test-test/src/com/sap/sailing/android/tracking/app/test/CheckinDataTest.java new file mode 100644 index 00000000000..0351eb3216d --- /dev/null +++ b/mobile/com.sap.sailing.android.tracking.app.test-test/src/com/sap/sailing/android/tracking/app/test/CheckinDataTest.java @@ -0,0 +1,67 @@ +package com.sap.sailing.android.tracking.app.test; + +import com.sap.sailing.android.tracking.app.valueobjects.CheckinData; +import com.sap.sailing.android.tracking.app.valueobjects.CompetitorInfo; +import com.sap.sailing.android.tracking.app.valueobjects.EventInfo; +import com.sap.sailing.android.tracking.app.valueobjects.LeaderboardInfo; + +import junit.framework.TestCase; + +public class CheckinDataTest extends TestCase { + + private CheckinData checkinData; + + @Override + protected void setUp() throws Exception { + checkinData = new CheckinData(); + + checkinData.leaderboardName = "lt"; + checkinData.eventId = "et"; + checkinData.eventName = "ent"; + checkinData.eventStartDateStr = "123"; + checkinData.eventEndDateStr = "456"; + checkinData.eventFirstImageUrl = "http://1.2.3.4/test.png"; + checkinData.eventServerUrl = "http://5.6.7.8"; + checkinData.checkinURL = "http://9.1.2.3/checkin"; + checkinData.competitorCountryCode = "DE"; + checkinData.competitorName = "cn"; + checkinData.competitorNationality = "GER"; + checkinData.competitorSailId = "SAIL 1"; + checkinData.competitorId = "1-2-3-4"; + checkinData.deviceUid = "0011223344"; + + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + public void testGetEvent() + { + EventInfo event = checkinData.getEvent(); + assertEquals("et", event.id); + assertEquals("ent", event.name); + assertEquals(123, event.startMillis); + assertEquals(456, event.endMillis); + assertEquals("http://1.2.3.4/test.png", event.imageUrl); + assertEquals("http://5.6.7.8", event.server); + } + + public void testGetCompetitor() + { + CompetitorInfo competitor = checkinData.getCompetitor(); + assertEquals("DE", competitor.countryCode); + assertEquals("cn", competitor.name); + assertEquals("GER", competitor.nationality); + assertEquals("SAIL 1", competitor.sailId); + assertEquals("1-2-3-4", competitor.id); + } + + public void testGetLeaderboard() + { + LeaderboardInfo leaderboard = checkinData.getLeaderboard(); + assertEquals("lt", leaderboard.name); + } +} diff --git a/mobile/com.sap.sailing.android.tracking.app/AndroidManifest.xml b/mobile/com.sap.sailing.android.tracking.app/AndroidManifest.xml index b2e509b56cb..07376d02587 100644 --- a/mobile/com.sap.sailing.android.tracking.app/AndroidManifest.xml +++ b/mobile/com.sap.sailing.android.tracking.app/AndroidManifest.xml @@ -52,6 +52,14 @@ + + + @@ -302,18 +302,40 @@ android:layout_alignParentTop="true" android:background="@color/white_transparent" /> - + android:layout_below="@id/white_bar" + android:orientation="horizontal" > + + + + + + + + + + diff --git a/mobile/com.sap.sailing.android.tracking.app/res/layout/fragment_regatta.xml b/mobile/com.sap.sailing.android.tracking.app/res/layout/fragment_regatta.xml index 7e9c65c14c2..c3b27fee7d1 100644 --- a/mobile/com.sap.sailing.android.tracking.app/res/layout/fragment_regatta.xml +++ b/mobile/com.sap.sailing.android.tracking.app/res/layout/fragment_regatta.xml @@ -287,19 +287,41 @@ android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:background="@color/white_transparent" /> - - + android:layout_below="@id/white_bar" + android:orientation="horizontal" > + + + + + Scan Race Barcode GPS Fix Interval Regatta + Leaderboard GPS Tracker einstellen Tracking Hello wörld! @@ -81,4 +82,5 @@ Kurs SOG Keine + Leaderboard \ No newline at end of file diff --git a/mobile/com.sap.sailing.android.tracking.app/res/values/strings.xml b/mobile/com.sap.sailing.android.tracking.app/res/values/strings.xml index d5e1be940b4..de6248ca714 100644 --- a/mobile/com.sap.sailing.android.tracking.app/res/values/strings.xml +++ b/mobile/com.sap.sailing.android.tracking.app/res/values/strings.xml @@ -21,6 +21,7 @@ Scan Race Barcode GPS fix interval Regatta + Leader board Setup GPS-Tracker Tracking Hello world! @@ -68,5 +69,6 @@ Heading SOG None + Leader board diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/TrackingService.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/TrackingService.java index 2f2db356da6..8fd7fb07987 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/TrackingService.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/TrackingService.java @@ -151,11 +151,11 @@ public class TrackingService extends Service implements ConnectionCallbacks, GPSQuality quality = GPSQuality.noSignal; if (gpsQualityListener != null) { - if (gpsAccurracy > 163) { + if (gpsAccurracy > 48) { quality = GPSQuality.poor; - } else if (gpsAccurracy > 48) { + } else if (gpsAccurracy > 10) { quality = GPSQuality.good; - } else if (gpsAccurracy < 48) { + } else if (gpsAccurracy <= 10) { quality = GPSQuality.great; } diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/TransmittingService.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/TransmittingService.java index 8eed352498a..914b3b9a137 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/TransmittingService.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/TransmittingService.java @@ -34,87 +34,92 @@ import com.sap.sailing.android.tracking.app.valueobjects.GpsFix; /** * Service that handles sending of GPS-fixes (fixes) to the Sailing-API. * - * It checks, after a certain time-interval has passed, if any fixes are available in the database - * and attempts to send them batch-wise. + * It checks, after a certain time-interval has passed, if any fixes are + * available in the database and attempts to send them batch-wise. * - * (There are two strategies for sending, depending on whether tracking is running. If it's running, - * only fixes for the current event are being sent. If not, it tries to send fixes for previous events, - * as well. Assuming there are older unsent fixes that for some reason have not been sent.) + * (There are two strategies for sending, depending on whether tracking is + * running. If it's running, only fixes for the current event are being sent. If + * not, it tries to send fixes for previous events, as well. Assuming there are + * older unsent fixes that for some reason have not been sent.) * - * It keeps track of when the last successful sending occurred and, if no new data is available - * (i.e., if tracking is disabled), ends itself after a certain period. It relies on being restarted - * by the {@link TrackingService}. + * It keeps track of when the last successful sending occurred and, if no new + * data is available (i.e., if tracking is disabled), ends itself after a + * certain period. It relies on being restarted by the {@link TrackingService}. * - * It also ends itself when connectivity is lost. In that case it relies on being restarted by - * the {@link ConnectivityChangedReceiver}. + * It also ends itself when connectivity is lost. In that case it relies on + * being restarted by the {@link ConnectivityChangedReceiver}. * - * It switches into a power saving mode, if a certain battery discharge is reached. It does, however, - * not do so, if the device is plugged in (= when it is charging). The power saving mode causes the - * time-interval between check- and transmit-cycles to be increased to conserve power. + * It switches into a power saving mode, if a certain battery discharge is + * reached. It does, however, not do so, if the device is plugged in (= when it + * is charging). The power saving mode causes the time-interval between check- + * and transmit-cycles to be increased to conserve power. * * @author Lukas Zielinski * */ public class TransmittingService extends Service { - + private static final String TAG = TransmittingService.class.getName(); - + private final int UPDATE_BATCH_SIZE = 1000; private final int UPDATE_INTERVAL_DEFAULT = 3000; private final int UPDATE_INTERVAL_POWERSAVE_MODE = 30000; - private final long AUTO_END_SERVICE_AFTER_NANO_PASSED = 10000000000L; // 10 sec + private final long AUTO_END_SERVICE_AFTER_NANO_PASSED = 10000000000L; // 10 + // sec private final float BATTERY_POWER_SAVE_TRESHOLD = 0.2f; - + private int currentUpdateInterval = UPDATE_INTERVAL_DEFAULT; - + private boolean sendingAttempted = false; private boolean lastTransmissionFailed = false; private long lastTransmissionTimestamp = 0; - + private AppPreferences prefs; private Timer timer; private boolean timerRunning; - - private APIConnectivityListener apiConnectivityListener; - private final IBinder transmittingBinder = new TransmittingBinder(); - + + private APIConnectivityListener apiConnectivityListener; + private final IBinder transmittingBinder = new TransmittingBinder(); + + /** + * True if sending, because if sending takes longer a new attempt should not + * be made. + */ + private static boolean currentlySending = false; + @Override public IBinder onBind(Intent intent) { prefs = new AppPreferences(this); - - if (!timerRunning) - { + + if (!timerRunning) { startTimer(); } - + return transmittingBinder; } - + @Override public int onStartCommand(Intent intent, int flags, int startId) { - + prefs = new AppPreferences(this); - + if (intent != null) { if (intent.getAction() != null) { if (intent.getAction().equals(getString(R.string.transmitting_service_start))) { - if (!timerRunning) - { - startTimer(); + if (!timerRunning) { + startTimer(); } } else { stopTimer(); } - } - else - { + } else { stopTimer(); } } - + return Service.START_STICKY; } - + private void startTimer() { timer = new Timer(); timer.start(); @@ -122,168 +127,151 @@ public class TransmittingService extends Service { if (BuildConfig.DEBUG) { ExLog.i(this, "TIMER", "Background update-timer start"); } - + } private void stopTimer() { - if (timer != null) - { - timer.stop(); + if (timer != null) { + timer.stop(); timerRunning = false; if (BuildConfig.DEBUG) { ExLog.i(this, "TIMER", "Background update-timer stop"); } - } } - - private void markSuccessfulTransmission() - { + + private void markSuccessfulTransmission() { + currentlySending = false; + if (BuildConfig.DEBUG) { ExLog.i(this, "TRANSMISSION", "markSuccessfulTransmission"); } - + lastTransmissionFailed = false; lastTransmissionTimestamp = System.nanoTime(); - + reportApiConnectivity(APIConnectivity.reachableTransmissionSuccess); } - - private void markFailedTransmission() - { + + private void markFailedTransmission() { + currentlySending = false; + if (BuildConfig.DEBUG) { ExLog.i(this, "TRANSMISSION", "markFailedTransmission"); } - + lastTransmissionFailed = true; lastTransmissionTimestamp = 0; - - if (isConnected()) - { + + if (isConnected()) { reportApiConnectivity(APIConnectivity.reachableTransmissionError); + } else { + reportApiConnectivity(APIConnectivity.notReachable); } - else - { - reportApiConnectivity(APIConnectivity.notReachable); - } - + } - + /** - * If time interval has passed without any transmission, the service can turn - * itself off. Same for + * If time interval has passed without any transmission, the service can + * turn itself off. Same for + * * @return */ - private boolean serviceCanShutItselfDown() - { + private boolean serviceCanShutItselfDown() { long currentNanoTime = System.nanoTime(); - - if (BuildConfig.DEBUG) - { + + if (BuildConfig.DEBUG) { ExLog.i(this, "TRANSMISSION", "serviceCanShutItselfDown?"); ExLog.i(this, "TRANSMISSION", "DELTA:" + (currentNanoTime - lastTransmissionTimestamp)); } - - // if network is not connected, shutdown. ConnevtivityChangedReceiver should restart us. - if (!isConnected()) - { - enabledConnectivityReceiver(); // make sure ConnectivityReceiver is running + + // if network is not connected, shutdown. ConnevtivityChangedReceiver + // should restart us. + if (!isConnected()) { + enabledConnectivityReceiver(); // make sure ConnectivityReceiver is + // running return true; } - - // if we had a failure, never go to sleep until data is sent successfully. - if (lastTransmissionFailed) - { - if (BuildConfig.DEBUG) - { - ExLog.i(this, "TRANSMISSION", "returning false, have failed transmission"); + + // if we had a failure, never go to sleep until data is sent + // successfully. + if (lastTransmissionFailed) { + if (BuildConfig.DEBUG) { + ExLog.i(this, "TRANSMISSION", "returning false, have failed transmission"); } - + return false; } - - if (!sendingAttempted) - { - if (BuildConfig.DEBUG) - { - ExLog.i(this, "TRANSMISSION", "returning true, no sending attempt"); + + if (!sendingAttempted) { + if (BuildConfig.DEBUG) { + ExLog.i(this, "TRANSMISSION", "returning true, no sending attempt"); } - + return true; } - - if (currentNanoTime - lastTransmissionTimestamp > AUTO_END_SERVICE_AFTER_NANO_PASSED) - { - if (BuildConfig.DEBUG) - { - ExLog.i(this, "TRANSMISSION", "returning true, timeout"); + + if (currentNanoTime - lastTransmissionTimestamp > AUTO_END_SERVICE_AFTER_NANO_PASSED) { + if (BuildConfig.DEBUG) { + ExLog.i(this, "TRANSMISSION", "returning true, timeout"); } - + return true; } - - if (BuildConfig.DEBUG) - { - ExLog.i(this, "TRANSMISSION", "returning false, don't terminate"); + + if (BuildConfig.DEBUG) { + ExLog.i(this, "TRANSMISSION", "returning false, don't terminate"); } - + return false; } - - private void timerFired() - { + + private void timerFired() { float batteryPct = getBatteryPercentage(); - boolean batteryIsCharging = prefs.getBatteryIsCharging(); - - if ( prefs.getEnergySavingEnabledByUser() || - (batteryPct < BATTERY_POWER_SAVE_TRESHOLD && !batteryIsCharging)) - { - if (BuildConfig.DEBUG) - { - ExLog.i(this, "POWER-LEVELS", "in power saving mode"); + boolean batteryIsCharging = prefs.getBatteryIsCharging(); + + if (prefs.getEnergySavingEnabledByUser() + || (batteryPct < BATTERY_POWER_SAVE_TRESHOLD && !batteryIsCharging)) { + if (BuildConfig.DEBUG) { + ExLog.i(this, "POWER-LEVELS", "in power saving mode"); } - + currentUpdateInterval = UPDATE_INTERVAL_POWERSAVE_MODE; - } - else - { - if (BuildConfig.DEBUG) - { - ExLog.i(this, "POWER-LEVELS", "in default power mode"); + } else { + if (BuildConfig.DEBUG) { + ExLog.i(this, "POWER-LEVELS", "in default power mode"); } - + currentUpdateInterval = UPDATE_INTERVAL_DEFAULT; } - + sendFixesToAPI(null); - reportUnsentGPSFixesCount(DatabaseHelper.getInstance().getNumberOfUnsentGPSFixes(getBaseContext())); + reportUnsentGPSFixesCount(DatabaseHelper.getInstance().getNumberOfUnsentGPSFixes( + getBaseContext())); } - - private boolean getTrackingServiceIsCurrentlyTracking() - { + + private boolean getTrackingServiceIsCurrentlyTracking() { return prefs.getTrackerIsTracking(); } - + private void sendFixesToAPI(List failedHosts) { - // first, lets fetch all unsent fixes - List fixes = DatabaseHelper.getInstance().getUnsentFixes(getBaseContext(), failedHosts, UPDATE_BATCH_SIZE); + List fixes = DatabaseHelper.getInstance().getUnsentFixes(getBaseContext(), + failedHosts, UPDATE_BATCH_SIZE); // store ids so we can delete the rows later ArrayList ids = new ArrayList(); - + // create JSON - JSONArray jsonArray = new JSONArray(); - String currentEventId = null; - String host = null; - - for (GpsFix fix : fixes) - { - if (currentEventId == null) - { + JSONArray jsonArray = new JSONArray(); + String currentEventId = null; + String host = null; + + for (GpsFix fix : fixes) { + if (currentEventId == null) { currentEventId = fix.eventId; host = fix.host; } - + if (currentEventId.equals(fix.eventId)) { // still have more unsent Gps-Fixes for this event. ids.add(String.valueOf(fix.id)); @@ -296,8 +284,7 @@ public class TransmittingService extends Service { json.put("longitude", fix.longitude); json.put("latitude", fix.latitude); } catch (JSONException ex) { - ExLog.i(this, TAG, "Error while building geolocation json " - + ex.getMessage()); + ExLog.i(this, TAG, "Error while building geolocation json " + ex.getMessage()); } jsonArray.put(json); @@ -306,197 +293,198 @@ public class TransmittingService extends Service { // collection, proceed with sending. break; } - + } if (jsonArray.length() > 0) { // send - String[] idsArr = new String[ids.size()]; JSONObject requestObject = new JSONObject(); - + try { requestObject.put("deviceUuid", UniqueDeviceUuid.getUniqueId(this)); requestObject.put("fixes", jsonArray); } catch (JSONException e) { e.printStackTrace(); } - + if (BuildConfig.DEBUG) { ExLog.i(this, TAG, "sending gps fix json: " + requestObject.toString()); - ExLog.i(this, TAG, "url: " + prefs.getServerURL() + prefs.getServerGpsFixesPostPath()); + ExLog.i(this, TAG, + "url: " + prefs.getServerURL() + prefs.getServerGpsFixesPostPath()); } - - if (host != null) - { - sendingAttempted = true; - - JsonObjectOrStatusOnlyRequest request = new JsonObjectOrStatusOnlyRequest(host - + prefs.getServerGpsFixesPostPath(), requestObject, new FixSubmitListener( - ids.toArray(idsArr)), new FixSubmitErrorListener(host, - getTrackingServiceIsCurrentlyTracking(), failedHosts)); - - VolleyHelper.getInstance(this).addRequest(request); - } - else - { + + if (host != null) { + if (currentlySending == true) + { + ExLog.w(this, TAG, "Warning, can't send fixes, currentlySending flag is set!"); + } + else + { + currentlySending = true; + sendingAttempted = true; // will be set to false in the + // listeners + JsonObjectOrStatusOnlyRequest request = new JsonObjectOrStatusOnlyRequest(host + + prefs.getServerGpsFixesPostPath(), requestObject, + new FixSubmitListener(ids.toArray(idsArr)), new FixSubmitErrorListener( + host, getTrackingServiceIsCurrentlyTracking(), failedHosts)); + + VolleyHelper.getInstance(this).addRequest(request); + } + } else { ExLog.w(this, TAG, "Warning, can't send fixes, host is null!"); } - + } else { - - if (serviceCanShutItselfDown()) - { + + if (serviceCanShutItselfDown()) { if (BuildConfig.DEBUG) { - ExLog.i(this, TAG, "Nothing to send or timeout occurred, Transmitting Service is stopping timer and terminating itself."); + ExLog.i(this, + TAG, + "Nothing to send or timeout occurred, Transmitting Service is stopping timer and terminating itself."); } - + stopTimer(); stopSelf(); } } } - + /** * Get a count of ALL unsent GPS-fixes. * - * This method is only interested in the GPS-fixes for the currently tracked event, - * and needs to find out the id of that event first. + * This method is only interested in the GPS-fixes for the currently tracked + * event, and needs to find out the id of that event first. + * * @return */ - - private float getBatteryPercentage() - { + + private float getBatteryPercentage() { IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); Intent batteryStatus = this.registerReceiver(null, ifilter); - + int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1); - float batteryPct = level / (float)scale; - + float batteryPct = level / (float) scale; + if (BuildConfig.DEBUG) { ExLog.i(this, TAG, "Battery: " + (batteryPct * 100) + "%"); } - + return batteryPct; } - - private void deleteSynced(String[] fixIdStrings) - { + + private void deleteSynced(String[] fixIdStrings) { DatabaseHelper.getInstance().deleteGpsFixes(getBaseContext(), fixIdStrings); } - + /** - * checks if there is network connectivity - * - * @return connectivity check value - */ - private boolean isConnected() { - ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo(); - if (activeNetwork == null) { - return false; - } - return activeNetwork.isConnected(); - } - - private void enabledConnectivityReceiver() - { - ConnectivityChangedReceiver.enable(this); - } - - /** - * Report API connectivity to listening activity - * @param apiConnectivity - */ - private void reportApiConnectivity( APIConnectivity apiConnectivity ) - { - if (apiConnectivityListener != null) - { - apiConnectivityListener.apiConnectivityUpdated( apiConnectivity ); - } - } - - /** - * Report the number of currently unsent GPS-fixes - * @param unsentGPSFixesCount - */ - private void reportUnsentGPSFixesCount( int unsentGPSFixesCount ) - { - if (apiConnectivityListener != null) - { - apiConnectivityListener.setUnsentGPSFixesCount(unsentGPSFixesCount); - } - } - -// might need this class in the future: -// -// private void markAsSynced(String[] fixIdStrings) -// { -// for (String idStr: fixIdStrings) -// { -// ContentValues updateValues = new ContentValues(); -// updateValues.put(SensorGps.GPS_SYNCED, 1); -// Uri uri = ContentUris.withAppendedId(SensorGps.CONTENT_URI, Long.parseLong(idStr)); -// getContentResolver().update(uri, updateValues, null, null); -// } -// } - - - + * checks if there is network connectivity + * + * @return connectivity check value + */ + private boolean isConnected() { + ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo(); + if (activeNetwork == null) { + return false; + } + return activeNetwork.isConnected(); + } + + private void enabledConnectivityReceiver() { + ConnectivityChangedReceiver.enable(this); + } + + /** + * Report API connectivity to listening activity + * + * @param apiConnectivity + */ + private void reportApiConnectivity(APIConnectivity apiConnectivity) { + if (apiConnectivityListener != null) { + apiConnectivityListener.apiConnectivityUpdated(apiConnectivity); + } + } + + /** + * Report the number of currently unsent GPS-fixes + * + * @param unsentGPSFixesCount + */ + private void reportUnsentGPSFixesCount(int unsentGPSFixesCount) { + if (apiConnectivityListener != null) { + apiConnectivityListener.setUnsentGPSFixesCount(unsentGPSFixesCount); + } + } + + // might need this class in the future: + // + // private void markAsSynced(String[] fixIdStrings) + // { + // for (String idStr: fixIdStrings) + // { + // ContentValues updateValues = new ContentValues(); + // updateValues.put(SensorGps.GPS_SYNCED, 1); + // Uri uri = ContentUris.withAppendedId(SensorGps.CONTENT_URI, + // Long.parseLong(idStr)); + // getContentResolver().update(uri, updateValues, null, null); + // } + // } + private class FixSubmitListener implements Listener { - private String[] ids; + private String[] ids; - public FixSubmitListener(String[] ids) { - this.ids = ids; - } - - @Override - public void onResponse(JSONObject response) { - deleteSynced(ids); - markSuccessfulTransmission(); - //markAsSynced(ids); - } - } - - private class FixSubmitErrorListener implements ErrorListener { - - private String host; - private List failedHosts; - private boolean currentlyTracking = false; - - public FixSubmitErrorListener(String host, boolean currentlyTracking, List failedHosts) { - this.failedHosts = failedHosts; - this.host = host; - this.currentlyTracking = true; + public FixSubmitListener(String[] ids) { + this.ids = ids; } - - @Override - public void onErrorResponse(VolleyError error) { - markFailedTransmission(); - ExLog.e(TransmittingService.this, TAG, "Error while sending GPS fix " + error.getMessage()); - - if (!currentlyTracking) - { - if (failedHosts == null) - { - failedHosts = new ArrayList(); - failedHosts.add(host); - } - - sendFixesToAPI(this.failedHosts); - ExLog.i(TransmittingService.this, TAG, "Retrying resend step with this failed hosts list: " + failedHosts); - } - } - } - + + @Override + public void onResponse(JSONObject response) { + deleteSynced(ids); + markSuccessfulTransmission(); + // markAsSynced(ids); + } + } + + private class FixSubmitErrorListener implements ErrorListener { + private String host; + private List failedHosts; + private boolean currentlyTracking = false; + + public FixSubmitErrorListener(String host, boolean currentlyTracking, + List failedHosts) { + this.failedHosts = failedHosts; + this.host = host; + this.currentlyTracking = true; + } + + @Override + public void onErrorResponse(VolleyError error) { + markFailedTransmission(); + ExLog.e(TransmittingService.this, TAG, + "Error while sending GPS fix " + error.getMessage()); + + if (!currentlyTracking) { + if (failedHosts == null) { + failedHosts = new ArrayList(); + failedHosts.add(host); + } + + sendFixesToAPI(this.failedHosts); + ExLog.i(TransmittingService.this, TAG, + "Retrying resend step with this failed hosts list: " + failedHosts); + } + } + } + class Timer implements Runnable { public Thread t; public volatile boolean endExecution; - + @Override public void run() { while (!endExecution) { @@ -505,7 +493,7 @@ public class TransmittingService extends Service { } catch (InterruptedException e) { e.printStackTrace(); } - + timerFired(); } reportApiConnectivity(APIConnectivity.noAttempt); @@ -518,50 +506,45 @@ public class TransmittingService extends Service { t.start(); } } - + public void stop() { endExecution = true; } } - - public void registerAPIConnectivityListener( APIConnectivityListener listener ) - { + + public void registerAPIConnectivityListener(APIConnectivityListener listener) { apiConnectivityListener = listener; } - - public void unregisterAPIConnectivityListener() - { + + public void unregisterAPIConnectivityListener() { apiConnectivityListener = null; } - + public class TransmittingBinder extends Binder { public TransmittingService getService() { return TransmittingService.this; } } - + public enum APIConnectivity { - notReachable (0), - reachableTransmissionSuccess (1), - reachableTransmissionError (2), - noAttempt (4); - + notReachable(0), reachableTransmissionSuccess(1), reachableTransmissionError(2), noAttempt( + 4); + private final int apiConnectivity; - - APIConnectivity(int connectivity) - { + + APIConnectivity(int connectivity) { this.apiConnectivity = connectivity; } - - public int toInt() - { + + public int toInt() { return this.apiConnectivity; } } - + public interface APIConnectivityListener { - public void apiConnectivityUpdated( APIConnectivity apiConnectivity ); - public void setUnsentGPSFixesCount( int count ); + public void apiConnectivityUpdated(APIConnectivity apiConnectivity); + + public void setUnsentGPSFixesCount(int count); } - + } diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/LeaderboardWebViewActivity.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/LeaderboardWebViewActivity.java new file mode 100644 index 00000000000..64d7e43c64c --- /dev/null +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/LeaderboardWebViewActivity.java @@ -0,0 +1,45 @@ +package com.sap.sailing.android.tracking.app.ui.activities; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.widget.Toolbar; + +import com.sap.sailing.android.tracking.app.R; +import com.sap.sailing.android.tracking.app.ui.fragments.LeaderboardFragment; + +public class LeaderboardWebViewActivity extends BaseActivity { + + public final static String LEADERBOARD_EXTRA_SERVER_URL = "leaderboardExtraServerUrl"; + public final static String LEADERBOARD_EXTRA_EVENT_ID = "leaderboardExtraEventId"; + public final static String LEADERBOARD_EXTRA_LEADERBOARD_NAME = "leaderboardExtraLeaderboardName"; + + public String serverUrl; + public String eventId; + public String leaderboardName; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + serverUrl = intent.getStringExtra(LEADERBOARD_EXTRA_SERVER_URL); + eventId = intent.getStringExtra(LEADERBOARD_EXTRA_EVENT_ID); + leaderboardName = intent.getStringExtra(LEADERBOARD_EXTRA_LEADERBOARD_NAME); + + setContentView(R.layout.fragment_container); + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + if (toolbar != null) { + setSupportActionBar(toolbar); + toolbar.setBackgroundColor(getResources().getColor(R.color.colorPrimary)); + } + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setHomeButtonEnabled(true); + toolbar.setNavigationIcon(R.drawable.sap_logo_64_sq); + toolbar.setPadding(20, 0, 0, 0); + getSupportActionBar().setTitle(getString(R.string.title_activity_webview)); + } + + replaceFragment(R.id.content_frame, new LeaderboardFragment()); + } +} diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/RegattaActivity.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/RegattaActivity.java index 4ebbeca3c54..19113f174a4 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/RegattaActivity.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/RegattaActivity.java @@ -45,10 +45,11 @@ import com.sap.sailing.android.tracking.app.valueobjects.LeaderboardInfo; public class RegattaActivity extends BaseActivity { private final static String TAG = RegattaActivity.class.getName(); + private final static String LEADERBOARD_IMAGE_FILENAME_PREFIX = "leaderboardImage_"; - private EventInfo event; - private CompetitorInfo competitor; - private LeaderboardInfo leaderboard; + public EventInfo event; + public CompetitorInfo competitor; + public LeaderboardInfo leaderboard; @Override @@ -85,7 +86,6 @@ public class RegattaActivity extends BaseActivity { } replaceFragment(R.id.content_frame, new RegattaFragment()); - } @Override @@ -102,10 +102,6 @@ public class RegattaActivity extends BaseActivity { ExLog.i(this, TAG, "Clicked SETTINGS."); startActivity(new Intent(this, SettingsActivity.class)); return true; -// case R.id.options_menu_info: -// ExLog.i(this, TAG, "Clicked INFO."); -// startActivity(new Intent(this, SystemInformationActivity.class)); -// return true; case R.id.options_menu_checkout: ExLog.i(this, TAG, "Clicked CHECKOUT."); checkout(); @@ -153,21 +149,22 @@ public class RegattaActivity extends BaseActivity { } getRegattaFragment().setChangePhotoButtonHidden(true); + ExLog.w(this, TAG, "Event Image URL: " + event.imageUrl); - if (event.imageUrl != null) + ImageView imageView = (ImageView) findViewById(R.id.userImage); + Bitmap storedImage = getStoredImage(getLeaderboardImageFileName(leaderboard.name)); + + if (storedImage == null) { - ImageView imageView = (ImageView) findViewById(R.id.userImage); - Bitmap storedImage = getStoredImage(); - if (storedImage != null) - { - imageView.setImageBitmap(storedImage); - userImageUpdated(); - } - else - { - new DownloadImageTask(imageView).execute(event.imageUrl); - } - + if (event.imageUrl != null) + { + new DownloadImageTask(imageView).execute(event.imageUrl); + } + } + else + { + imageView.setImageBitmap(storedImage); + userImageUpdated(); } super.onResume(); @@ -177,9 +174,9 @@ public class RegattaActivity extends BaseActivity { * * @param bitmap */ - public void updatePictureChosenByUser(final Bitmap bitmap) + public void updateLeaderboardPictureChosenByUser(final Bitmap bitmap) { - storeImage(bitmap); + storeImage(bitmap, getLeaderboardImageFileName(leaderboard.name)); runOnUiThread(new Runnable() { @Override @@ -196,8 +193,8 @@ public class RegattaActivity extends BaseActivity { * Store image for quicker retrieval later. * @param images */ - private void storeImage(Bitmap image) { - File pictureFile = getMediaFile(); + private void storeImage(Bitmap image, String fileName) { + File pictureFile = getImageFile(fileName); if (pictureFile == null) { Log.d(TAG, "Error creating media file, check storage permissions: ");// e.getMessage()); @@ -215,12 +212,12 @@ public class RegattaActivity extends BaseActivity { } /** - * Get leaderboard image if there's one saved. + * Get stored image if there's one saved. * @return */ - private Bitmap getStoredImage() + private Bitmap getStoredImage(String fileName) { - File pictureFile = getMediaFile(); + File pictureFile = getImageFile(fileName); if (pictureFile == null) { return null; @@ -228,21 +225,44 @@ public class RegattaActivity extends BaseActivity { BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.ARGB_8888; - Bitmap image = BitmapFactory.decodeFile(pictureFile.getAbsolutePath(), - options); + Bitmap image = BitmapFactory.decodeFile(pictureFile.getAbsolutePath(), options); return image; } + +// private File getMediaFile(){ +// +// +// } + /** * Get Path for cached leaderbaord image. * @return */ - private File getMediaFile(){ + public File getImageFile(String fileName) { - File mediaStorageDir; + File mediaStorageDir = getMediaStorageDir(); - if (Environment.MEDIA_MOUNTED.equals(Environment - .getExternalStorageState())) { + File mediaFile; + String mImageName="MI_"+ fileName +".png"; + mediaFile = new File(mediaStorageDir.getPath() + File.separator + mImageName); + return mediaFile; + } + + public void deleteImageFile(String fileName) { + File mediaStorageDir = getMediaStorageDir(); + + File mediaFile; + String mImageName = "MI_" + fileName + ".png"; + mediaFile = new File(mediaStorageDir.getPath() + File.separator + mImageName); + + mediaFile.delete(); + } + + public File getMediaStorageDir() { + File mediaStorageDir; + + if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { mediaStorageDir = new File( Environment.getExternalStorageDirectory() + "/Android/data/" @@ -257,12 +277,9 @@ public class RegattaActivity extends BaseActivity { return null; } } - - File mediaFile; - String mImageName="MI_"+ leaderboard.name +".png"; - mediaFile = new File(mediaStorageDir.getPath() + File.separator + mImageName); - return mediaFile; - } + + return mediaStorageDir; + } private class DownloadImageTask extends AsyncTask { ImageView bmImage; @@ -274,6 +291,7 @@ public class RegattaActivity extends BaseActivity { protected Bitmap doInBackground(String... urls) { String urldisplay = urls[0]; Bitmap mIcon11 = null; + try { InputStream in = new java.net.URL(urldisplay).openStream(); mIcon11 = BitmapFactory.decodeStream(in); @@ -285,11 +303,15 @@ public class RegattaActivity extends BaseActivity { protected void onPostExecute(Bitmap result) { bmImage.setImageBitmap(result); - storeImage(result); + storeImage(result, getLeaderboardImageFileName(leaderboard.name)); userImageUpdated(); } } + private String getLeaderboardImageFileName(String leaderboardName) { + return LEADERBOARD_IMAGE_FILENAME_PREFIX + leaderboardName; + } + /** * Check out from regatta; */ @@ -320,6 +342,7 @@ public class RegattaActivity extends BaseActivity { public void onResponse(JSONObject response) { DatabaseHelper.getInstance().deleteRegattaFromDatabase( RegattaActivity.this, event, competitor, leaderboard); + deleteImageFile(getLeaderboardImageFileName(leaderboard.name)); dismissProgressDialog(); finish(); } @@ -331,15 +354,23 @@ public class RegattaActivity extends BaseActivity { showErrorPopup( R.string.error, R.string.error_could_not_complete_operation_on_server_try_again); - } }); VolleyHelper.getInstance(this).addRequest(checkoutRequest); } - public EventInfo getEvent() - { - return event; + @Override + public void onBackPressed() { + RegattaFragment fragment = (RegattaFragment) getSupportFragmentManager().findFragmentById(R.id.content_frame); + + if (fragment.isShowingBigCheckoutButton()) + { + //do nothing, user must checkout himself at this point. + } + else + { + super.onBackPressed(); + } } } diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/TrackingActivity.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/TrackingActivity.java index 2535c94e10f..08dfa5f8eaf 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/TrackingActivity.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/TrackingActivity.java @@ -14,9 +14,7 @@ import android.support.v4.app.FragmentStatePagerAdapter; import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager.OnPageChangeListener; import android.support.v7.widget.Toolbar; -import android.view.MotionEvent; import android.widget.TextView; -import android.widget.Toast; import com.sap.sailing.android.shared.logging.ExLog; import com.sap.sailing.android.tracking.app.BuildConfig; @@ -52,6 +50,9 @@ public class TrackingActivity extends BaseActivity implements GPSQualityListener private final static String TAG = TrackingActivity.class.getName(); private final static String SIS_TRACKING_FRAGMENT = "savedInstanceTrackingFragment"; + private final static String SIS_LAST_VIEWPAGER_ITEM = "instanceStateLastViewPagerItem"; + private final static String SIS_LAST_SPEED_TEXT = "instanceStateLastSpeedText"; + private final static String SIS_LAST_COMPASS_TEXT = "instanceStateLastCompassText"; private ViewPager mPager; private ScreenSlidePagerAdapter mPagerAdapter; @@ -59,9 +60,20 @@ public class TrackingActivity extends BaseActivity implements GPSQualityListener private String eventId; private AppPreferences prefs; - private TrackingFragment trackingFragment; - + private TrackingFragment trackingFragment; private TimerRunnable timer; + + private int lastViewPagerItem; + + /** + * This isn't nice. The callbacks for fragments inside a + * view pager are unreliable, but I want the values + * to be displayed immediately after device rotation. + * Thus they are cached here and the fragments can pick + * them up. + */ + public String lastSpeedIndicatorText = ""; + public String lastCompassIndicatorText = ""; @Override protected void onCreate(Bundle savedInstanceState) { @@ -101,12 +113,8 @@ public class TrackingActivity extends BaseActivity implements GPSQualityListener mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager()); } mPager.setAdapter(mPagerAdapter); - mPager.setOffscreenPageLimit(3); + mPager.setOffscreenPageLimit(0); - // ok, here be dragons. - // however, I haven't found out how the ViewPager retains and dismisses fragments, so I check for every case. - // The easy way (if savedInstanceState != null) takeFromBundle(); doesn't work, since only some fragments are - // saved to the bundle it seems. if (savedInstanceState != null) { Fragment tFragment = getSupportFragmentManager().getFragment(savedInstanceState, SIS_TRACKING_FRAGMENT); if (tFragment != null) { @@ -114,6 +122,10 @@ public class TrackingActivity extends BaseActivity implements GPSQualityListener } else { trackingFragment = new TrackingFragment(); } + + lastViewPagerItem = savedInstanceState.getInt(SIS_LAST_VIEWPAGER_ITEM); + lastSpeedIndicatorText = savedInstanceState.getString(SIS_LAST_SPEED_TEXT); + lastCompassIndicatorText = savedInstanceState.getString(SIS_LAST_COMPASS_TEXT); } else { trackingFragment = new TrackingFragment(); } @@ -125,6 +137,7 @@ public class TrackingActivity extends BaseActivity implements GPSQualityListener @Override public void onPageSelected(int arg0) { + lastViewPagerItem = arg0; } @Override @@ -140,23 +153,17 @@ public class TrackingActivity extends BaseActivity implements GPSQualityListener ServiceHelper.getInstance().startTrackingService(this, eventId); } - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - // HudFragment hudFragment = (HudFragment) - // getSupportFragmentManager().findFragmentById(R.id.hud_content_frame); - // hudFragment.layoutOverlay(); - // - // hudFragment.setSpeedOverGround(0); - // hudFragment.setHeading(0); - } - @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); + + outState.putInt(SIS_LAST_VIEWPAGER_ITEM, lastViewPagerItem); + outState.putString(SIS_LAST_SPEED_TEXT, lastSpeedIndicatorText); + outState.putString(SIS_LAST_COMPASS_TEXT, lastCompassIndicatorText); + if (trackingFragment.isAdded()) { getSupportFragmentManager().putFragment(outState, SIS_TRACKING_FRAGMENT, trackingFragment); - } + } } @Override @@ -219,7 +226,9 @@ public class TrackingActivity extends BaseActivity implements GPSQualityListener { mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager()); } + mPager.setAdapter(mPagerAdapter); + mPager.setCurrentItem(lastViewPagerItem); } @Override @@ -367,14 +376,6 @@ public class TrackingActivity extends BaseActivity implements GPSQualityListener return 3; } -// public StopTrackingButtonFragment getStopTrackingButtonFragment() { -// if (stbFragment != null && stbFragment.isAdded()) -// { -// return stbFragment; -// } -// else return null; -// } - public CompassFragment getCompassFragment() { if (cFragment != null && cFragment.isAdded()) { diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/CompassFragment.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/CompassFragment.java index 32f20886dd7..b69432b3203 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/CompassFragment.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/CompassFragment.java @@ -7,11 +7,11 @@ import android.view.ViewGroup; import android.widget.TextView; import com.sap.sailing.android.tracking.app.R; +import com.sap.sailing.android.tracking.app.ui.activities.TrackingActivity; public class CompassFragment extends BaseFragment { - private String TAG = CompassFragment.class.getName(); - private final String SIS_HEADING_TEXTIVEW = "CompassFragmentHeadingTextView"; + //private String TAG = CompassFragment.class.getName(); public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); @@ -25,6 +25,12 @@ public class CompassFragment extends BaseFragment { { TextView headingText = (TextView) getActivity().findViewById(R.id.compass_bearing_text_view); headingText.setText(String.valueOf(Math.round(heading)) + "°"); + + TrackingActivity activity = (TrackingActivity) getActivity(); + + if (activity != null) { + activity.lastCompassIndicatorText = headingText.getText().toString(); + } } } @@ -32,20 +38,12 @@ public class CompassFragment extends BaseFragment { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); TextView headingText = (TextView)getActivity().findViewById(R.id.compass_bearing_text_view); - if (savedInstanceState != null) - { - headingText.setText(savedInstanceState.getString(SIS_HEADING_TEXTIVEW)); + + TrackingActivity activity = (TrackingActivity) getActivity(); + if (activity != null) { + headingText.setText(activity.lastCompassIndicatorText); + } else { + headingText.setText(""); } - else - { - headingText.setText("---°"); - } - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - TextView headingText = (TextView)getActivity().findViewById(R.id.compass_bearing_text_view); - outState.putString(SIS_HEADING_TEXTIVEW, headingText.getText().toString()); - } + } } diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/HomeFragment.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/HomeFragment.java index 98223ffb57f..8abef5d0fbe 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/HomeFragment.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/HomeFragment.java @@ -2,7 +2,6 @@ package com.sap.sailing.android.tracking.app.ui.fragments; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; -import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.UUID; @@ -14,19 +13,13 @@ import org.json.JSONObject; import android.annotation.SuppressLint; import android.app.Activity; import android.app.AlertDialog; -import android.content.ContentProviderOperation; -import android.content.ContentResolver; -import android.content.ContentValues; import android.content.DialogInterface; import android.content.Intent; -import android.content.OperationApplicationException; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; -import android.os.RemoteException; -import android.provider.BaseColumns; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; @@ -49,17 +42,17 @@ import com.sap.sailing.android.tracking.app.BuildConfig; import com.sap.sailing.android.tracking.app.R; import com.sap.sailing.android.tracking.app.adapter.RegattaAdapter; import com.sap.sailing.android.tracking.app.provider.AnalyticsContract; -import com.sap.sailing.android.tracking.app.provider.AnalyticsContract.Competitor; import com.sap.sailing.android.tracking.app.provider.AnalyticsContract.Event; -import com.sap.sailing.android.tracking.app.provider.AnalyticsContract.Leaderboard; import com.sap.sailing.android.tracking.app.ui.activities.RegattaActivity; import com.sap.sailing.android.tracking.app.ui.activities.StartActivity; import com.sap.sailing.android.tracking.app.utils.AppPreferences; import com.sap.sailing.android.tracking.app.utils.CheckinHelper; import com.sap.sailing.android.tracking.app.utils.DatabaseHelper; +import com.sap.sailing.android.tracking.app.utils.DatabaseHelper.GeneralDatabaseHelperException; import com.sap.sailing.android.tracking.app.utils.JsonObjectOrStatusOnlyRequest; import com.sap.sailing.android.tracking.app.utils.UniqueDeviceUuid; import com.sap.sailing.android.tracking.app.utils.VolleyHelper; +import com.sap.sailing.android.tracking.app.valueobjects.CheckinData; import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier; import com.sap.sailing.domain.racelog.tracking.impl.SmartphoneUUIDIdentifierImpl; @@ -83,8 +76,6 @@ public class HomeFragment extends BaseFragment implements prefs = new AppPreferences(getActivity()); - getActivity().getContentResolver(); - View view = inflater.inflate(R.layout.fragment_home, container, false); Button scanButton = (Button) view.findViewById(R.id.scanQr); @@ -103,8 +94,7 @@ public class HomeFragment extends BaseFragment implements listView.addHeaderView(inflater.inflate( R.layout.regatta_listview_header, null)); - adapter = new RegattaAdapter(getActivity(), - R.layout.ragatta_listview_row, null, 0); + adapter = new RegattaAdapter(getActivity(), R.layout.ragatta_listview_row, null, 0); listView.setAdapter(adapter); listView.setOnItemClickListener(new ItemClickListener()); } @@ -184,17 +174,13 @@ public class HomeFragment extends BaseFragment implements String leaderboardNameFromQR; try { - leaderboardNameFromQR = URLEncoder.encode( - uri.getQueryParameter(CheckinHelper.LEADERBOARD_NAME), - "UTF-8").replace("+", "%20"); + leaderboardNameFromQR = URLEncoder.encode(uri.getQueryParameter(CheckinHelper.LEADERBOARD_NAME),"UTF-8").replace("+", "%20"); } catch (UnsupportedEncodingException e) { - ExLog.e(getActivity(), TAG, "Failed to encode leaderboard name: " - + e.getMessage()); + ExLog.e(getActivity(), TAG, "Failed to encode leaderboard name: " + e.getMessage()); leaderboardNameFromQR = ""; } - final String competitorId = uri - .getQueryParameter(CheckinHelper.COMPETITOR_ID); + final String competitorId = uri.getQueryParameter(CheckinHelper.COMPETITOR_ID); final String checkinURLStr = prefs.getServerURL() + prefs.getServerCheckinPath().replace("{leaderboard-name}", leaderboardNameFromQR); @@ -435,30 +421,6 @@ public class HomeFragment extends BaseFragment implements alert.show(); } - /** - * Return true if the combination of eventId, leaderboardName and - * competitorId does not exist in the DB. - * - * @param eventId - * @param leaderboardName - * @param competitorId - * @return - */ - private boolean eventLeaderboardCompetitorCombnationAvailable( - String eventId, String leaderboardName, String competitorId) { - - ContentResolver cr = getActivity().getContentResolver(); - String sel = "leaderboards.leaderboard_name = \"" + leaderboardName - + "\" AND competitors.competitor_id = \"" + competitorId - + "\" AND events.event_id = \"" + eventId + "\""; - - int count = cr.query( - AnalyticsContract.EventLeaderboardCompetitorJoined.CONTENT_URI, - null, sel, null, null).getCount(); - - return count == 0; - } - /** * Perform a checkin request and launch RegattaAcitivity afterwards * @@ -468,74 +430,21 @@ public class HomeFragment extends BaseFragment implements */ private void checkInWithAPIAndDisplayTrackingActivity( CheckinData checkinData) { - if (eventLeaderboardCompetitorCombnationAvailable(checkinData.eventId, - checkinData.leaderboardName, checkinData.competitorId)) { - - // inserting leaderboard first in order to get the ID. - // This should be atomic, but couldn't get withValueBackReference to - // work yet. - - ContentResolver cr = getActivity().getContentResolver(); - - ContentValues clv = new ContentValues(); - clv.put(Leaderboard.LEADERBOARD_NAME, checkinData.leaderboardName); - cr.insert(Leaderboard.CONTENT_URI, clv); - - Cursor cur = cr.query(Leaderboard.CONTENT_URI, null, null, null, null); - long lastLeaderboardId = 0; - - if (cur.moveToLast()) { - lastLeaderboardId = cur.getLong(cur - .getColumnIndex(BaseColumns._ID)); - } - - cur.close(); - - // now, with the leaderboard id, insert event and competitor - // todo: fix this so its atomic. - - ArrayList opList = new ArrayList(); - - ContentValues cev = new ContentValues(); - cev.put(Event.EVENT_ID, checkinData.eventId); - cev.put(Event.EVENT_NAME, checkinData.eventName); - cev.put(Event.EVENT_DATE_START, Long.parseLong(checkinData.eventStartDateStr)); - cev.put(Event.EVENT_DATE_END, Long.parseLong(checkinData.eventEndDateStr)); - cev.put(Event.EVENT_SERVER, checkinData.eventServerUrl); - cev.put(Event.EVENT_IMAGE_URL, checkinData.eventFirstImageUrl); - cev.put(Event.EVENT_LEADERBOARD_FK, lastLeaderboardId); - - opList.add(ContentProviderOperation.newInsert(Event.CONTENT_URI) - .withValues(cev).build()); - - ContentValues ccv = new ContentValues(); - - ccv.put(Competitor.COMPETITOR_COUNTRY_CODE,checkinData.competitorCountryCode); - ccv.put(Competitor.COMPETITOR_DISPLAY_NAME,checkinData.competitorName); - ccv.put(Competitor.COMPETITOR_ID, checkinData.competitorId); - ccv.put(Competitor.COMPETITOR_NATIONALITY,checkinData.competitorNationality); - ccv.put(Competitor.COMPETITOR_SAIL_ID, checkinData.competitorSailId); - ccv.put(Competitor.COMPETITOR_LEADERBOARD_FK, lastLeaderboardId); - - opList.add(ContentProviderOperation - .newInsert(Competitor.CONTENT_URI).withValues(ccv).build()); + if (DatabaseHelper.getInstance().eventLeaderboardCompetitorCombnationAvailable( + getActivity(), checkinData.eventId, checkinData.leaderboardName, checkinData.competitorId)) { try { - cr.applyBatch(AnalyticsContract.CONTENT_AUTHORITY, opList); + DatabaseHelper.getInstance().storeCheckinRow(getActivity(), checkinData.getEvent(), + checkinData.getCompetitor(), checkinData.getLeaderboard()); adapter.notifyDataSetChanged(); - } catch (RemoteException e1) { - ExLog.e(getActivity(), TAG, "Batch insert failed: " + e1.getMessage()); - displayDatabaseError(); - return; - } catch (OperationApplicationException e1) { - ExLog.e(getActivity(), TAG, "Batch insert failed: " + e1.getMessage()); + } catch (GeneralDatabaseHelperException e) { + ExLog.e(getActivity(), TAG, "Batch insert failed: " + e.getMessage()); displayDatabaseError(); return; } if (BuildConfig.DEBUG) { - ExLog.i(getActivity(), TAG, - "Batch-insert of checkinData completed."); + ExLog.i(getActivity(), TAG, "Batch-insert of checkinData completed."); } } else { ExLog.w(getActivity(), TAG, @@ -554,7 +463,6 @@ public class HomeFragment extends BaseFragment implements Date date = new Date(); StartActivity startActivity = (StartActivity)getActivity(); - startActivity.showProgressDialog(R.string.please_wait, R.string.checking_in); try { @@ -625,8 +533,7 @@ public class HomeFragment extends BaseFragment implements * @param regattaName * @param eventName */ - private void startRegatta(String leaderboardName, String eventId, - String competitorId) { + private void startRegatta(String leaderboardName, String eventId, String competitorId) { Intent intent = new Intent(getActivity(), RegattaActivity.class); intent.putExtra(getString(R.string.leaderboard_name), leaderboardName); intent.putExtra(getString(R.string.event_id), eventId); @@ -638,7 +545,8 @@ public class HomeFragment extends BaseFragment implements public Loader onCreateLoader(int loaderId, Bundle bundle) { switch (loaderId) { case REGATTA_LOADER: - String[] projection = new String[] { "events.event_id", + String[] projection = new String[] { + "events.event_id", "events._id", "events.event_name", "events.event_server", "competitors.competitor_display_name", "competitors.competitor_id", @@ -678,25 +586,7 @@ public class HomeFragment extends BaseFragment implements break; } } - - private class CheckinData { - // public String gcmId; - public String leaderboardName; - public String eventId; - public String eventName; - public String eventStartDateStr; - public String eventEndDateStr; - public String eventFirstImageUrl; - public String eventServerUrl; - public String checkinURL; - public String competitorName; - public String competitorId; - public String competitorSailId; - public String competitorNationality; - public String competitorCountryCode; - public String deviceUid; - } - + private class ClickListener implements OnClickListener { @Override @@ -757,6 +647,7 @@ public class HomeFragment extends BaseFragment implements public void onResponse(JSONObject response) { StartActivity startActivity = (StartActivity)getActivity(); startActivity.dismissProgressDialog(); + startRegatta(leaderboardName, eventId, competitorId); } } diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/LeaderboardFragment.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/LeaderboardFragment.java new file mode 100644 index 00000000000..6c3abce3e4d --- /dev/null +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/LeaderboardFragment.java @@ -0,0 +1,67 @@ +package com.sap.sailing.android.tracking.app.ui.fragments; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.Toast; +import com.sap.sailing.android.shared.logging.ExLog; +import com.sap.sailing.android.tracking.app.R; +import com.sap.sailing.android.tracking.app.ui.activities.LeaderboardWebViewActivity; + +public class LeaderboardFragment extends BaseFragment { + + private static final String TAG = LeaderboardFragment.class.getName(); + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + super.onCreateView(inflater, container, savedInstanceState); + + View view = inflater.inflate(R.layout.fragment_leaderboard, container, false); + return view; + } + + @SuppressLint("SetJavaScriptEnabled") + @Override + public void onResume() { + super.onResume(); + + WebView webview = (WebView)getActivity().findViewById(R.id.web_view); + + WebSettings webSettings = webview.getSettings(); + webSettings.setJavaScriptEnabled(true); + + webview.setWebViewClient(new WebViewClient() { + public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { + Toast.makeText(getActivity(), description, Toast.LENGTH_SHORT).show(); + } + }); + + try { + String url = getLeaderboardURL(); + webview.loadUrl(url); + } catch (UnsupportedEncodingException e) { + LeaderboardWebViewActivity activity = (LeaderboardWebViewActivity)getActivity(); + activity.showErrorPopup(R.string.error, R.string.error_could_not_complete_operation_on_server_try_again); + ExLog.e(getActivity(), TAG, "UnsupportedEncodingException when creating leaderboard-url: " + e); + } + } + + private String getLeaderboardURL() throws UnsupportedEncodingException + { + LeaderboardWebViewActivity activity = (LeaderboardWebViewActivity)getActivity(); + String eventId = URLEncoder.encode(activity.eventId, "UTF-8"); + String leaderboardName = URLEncoder.encode(activity.leaderboardName, "UTF-8"); + String serverUrl = activity.serverUrl; + + String formatString = "%s/gwt/Home.html#LeaderboardPlace:eventId=%s" + + "&leaderboardName=%s&showRaceDetails=true&showSettings=true"; + return String.format(formatString, serverUrl, eventId, leaderboardName); + } +} diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/RegattaFragment.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/RegattaFragment.java index 39de24932eb..a1b93f740a2 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/RegattaFragment.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/RegattaFragment.java @@ -1,6 +1,8 @@ package com.sap.sailing.android.tracking.app.ui.fragments; +import java.io.File; import java.io.FileNotFoundException; +import java.io.IOException; import java.io.InputStream; import java.util.concurrent.TimeUnit; @@ -24,7 +26,10 @@ import android.widget.RelativeLayout; import android.widget.TextView; import com.sap.sailing.android.shared.logging.ExLog; +import com.sap.sailing.android.tracking.app.BuildConfig; import com.sap.sailing.android.tracking.app.R; +import com.sap.sailing.android.tracking.app.provider.AnalyticsContract.Event; +import com.sap.sailing.android.tracking.app.ui.activities.LeaderboardWebViewActivity; import com.sap.sailing.android.tracking.app.ui.activities.RegattaActivity; import com.sap.sailing.android.tracking.app.ui.activities.TrackingActivity; @@ -34,7 +39,11 @@ public class RegattaFragment extends BaseFragment implements OnClickListener { private final int CAMERA_REQUEST_CODE = 3118; private final int SELECT_PHOTO_REQUEST_CODE = 1693; - + private final int IMAGE_MAX_SIZE = 2000; + + private final String CAMERA_TEMP_FILE = "cameraTempFile"; + + private boolean showingThankYouNote; private TimerRunnable timer; @@ -48,6 +57,9 @@ public class RegattaFragment extends BaseFragment implements OnClickListener { Button startTrackingButton = (Button) view.findViewById(R.id.start_tracking); startTrackingButton.setOnClickListener(this); + + Button showLeaderboardButton = (Button) view.findViewById(R.id.show_leaderboards_button); + showLeaderboardButton.setOnClickListener(this); Button changePhotoButton = (Button) view.findViewById(R.id.change_photo_button); changePhotoButton.setOnClickListener(this); @@ -63,7 +75,7 @@ public class RegattaFragment extends BaseFragment implements OnClickListener { private void checkAndSwitchToThankYouScreenIfRegattaOver() { RegattaActivity regattaActivity = (RegattaActivity) getActivity(); - long regattaEnd = regattaActivity.getEvent().endMillis; + long regattaEnd = regattaActivity.event.endMillis; if (System.currentTimeMillis() > regattaEnd) { switchToThankYouScreen(); @@ -77,7 +89,7 @@ public class RegattaFragment extends BaseFragment implements OnClickListener { TextView textView = (TextView)getActivity().findViewById(R.id.regatta_starts_in); RegattaActivity regattaActivity = (RegattaActivity) getActivity(); - long regattaStart = regattaActivity.getEvent().startMillis; + long regattaStart = regattaActivity.event.startMillis; LinearLayout threeBoxesLayout = (LinearLayout) getActivity() .findViewById(R.id.three_boxes_regatta_starts); @@ -94,6 +106,10 @@ public class RegattaFragment extends BaseFragment implements OnClickListener { } } + public boolean isShowingBigCheckoutButton() { + return showingThankYouNote; + } + private void switchToThankYouScreen() { showingThankYouNote = true; @@ -130,6 +146,9 @@ public class RegattaFragment extends BaseFragment implements OnClickListener { @Override public void onClick(View view) { switch (view.getId()) { + case R.id.show_leaderboards_button: + startLeaderboardActivity(); + break; case R.id.start_tracking: if (showingThankYouNote) { RegattaActivity regattaActivity = (RegattaActivity) getActivity(); @@ -198,6 +217,8 @@ public class RegattaFragment extends BaseFragment implements OnClickListener { private void showTakePhotoActivity() { Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + File photoFile = ((RegattaActivity)getActivity()).getImageFile(CAMERA_TEMP_FILE); + cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile)); startActivityForResult(cameraIntent, CAMERA_REQUEST_CODE); } @@ -207,39 +228,99 @@ public class RegattaFragment extends BaseFragment implements OnClickListener { RegattaActivity activity = (RegattaActivity) getActivity(); if (requestCode == CAMERA_REQUEST_CODE) { - Bitmap photo = (Bitmap) data.getExtras().get("data"); - activity.updatePictureChosenByUser(photo); + File photoFile = ((RegattaActivity)getActivity()).getImageFile(CAMERA_TEMP_FILE); + Bitmap photo; + try { + photo = decodeUri(Uri.fromFile(photoFile)); + activity.updateLeaderboardPictureChosenByUser(photo); + } catch (FileNotFoundException e) { + if (BuildConfig.DEBUG) + { + ExLog.i(getActivity(), TAG, "update photo, file not found: " + photoFile); + } + } catch (IOException e) { + if (BuildConfig.DEBUG) + { + ExLog.i(getActivity(), TAG, "update photo, io exception: " + e.getMessage()); + } + } finally { + ((RegattaActivity)getActivity()).deleteFile(CAMERA_TEMP_FILE); + } } else if (requestCode == SELECT_PHOTO_REQUEST_CODE) { Uri selectedImage = data.getData(); - InputStream imageStream; + try { - imageStream = getActivity().getContentResolver() - .openInputStream(selectedImage); - Bitmap photo = BitmapFactory.decodeStream(imageStream); - activity.updatePictureChosenByUser(photo); + Bitmap photo = decodeUri(selectedImage); + activity.updateLeaderboardPictureChosenByUser(photo); } catch (FileNotFoundException e) { ExLog.e(getActivity(), TAG, "File not found exception after picking image from gallery"); - return; + } catch (IOException e) { + if (BuildConfig.DEBUG) + { + ExLog.i(getActivity(), TAG, "update photo, io exception: " + e.getMessage()); + } } } } super.onActivityResult(requestCode, resultCode, data); } + + /** + * Decode image with maximum size + * @param uri + * @return + * @throws IOException + */ + private Bitmap decodeUri(Uri uri) throws IOException{ + Bitmap bitmap = null; + + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + + InputStream fis = getActivity().getContentResolver().openInputStream(uri); + BitmapFactory.decodeStream(fis, null, options); + fis.close(); + + int scale = 1; + if (options.outHeight > IMAGE_MAX_SIZE || options.outWidth > IMAGE_MAX_SIZE) { + scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE / + (double) Math.max(options.outHeight, options.outWidth)) / Math.log(0.5))); + } + + BitmapFactory.Options options2 = new BitmapFactory.Options(); + options2.inSampleSize = scale; + fis = getActivity().getContentResolver().openInputStream(uri); + bitmap = BitmapFactory.decodeStream(fis, null, options2); + fis.close(); + + return bitmap; + } + + private void startLeaderboardActivity() { + RegattaActivity activity = (RegattaActivity)getActivity(); + + Intent intent = new Intent(getActivity(), LeaderboardWebViewActivity.class); + intent.putExtra(LeaderboardWebViewActivity.LEADERBOARD_EXTRA_SERVER_URL, activity.event.server); + intent.putExtra(LeaderboardWebViewActivity.LEADERBOARD_EXTRA_EVENT_ID, activity.event.id); + intent.putExtra(LeaderboardWebViewActivity.LEADERBOARD_EXTRA_LEADERBOARD_NAME, activity.leaderboard.name); + + getActivity().startActivity(intent); + } private void startTrackingActivity() { RegattaActivity regattaActivity = (RegattaActivity) getActivity(); Intent intent = new Intent(getActivity(), TrackingActivity.class); intent.putExtra( getString(R.string.tracking_activity_event_id_parameter), - regattaActivity.getEvent().id); + regattaActivity.event.id); getActivity().startActivity(intent); } private void timerFired() { RegattaActivity regattaActivity = (RegattaActivity) getActivity(); - updateCountdownTimer(regattaActivity.getEvent().startMillis); + updateCountdownTimer(regattaActivity.event.startMillis); } private void updateCountdownTimer(long startTime) { @@ -258,19 +339,13 @@ public class RegattaFragment extends BaseFragment implements OnClickListener { } private void setCountdownTime(int days, int hours, int minutes) { - TextView daysTextView = (TextView) getActivity().findViewById( - R.id.starts_in_days); - TextView hoursTextView = (TextView) getActivity().findViewById( - R.id.starts_in_hours); - TextView minutesTextView = (TextView) getActivity().findViewById( - R.id.starts_in_minutes); + TextView daysTextView = (TextView) getActivity().findViewById(R.id.starts_in_days); + TextView hoursTextView = (TextView) getActivity().findViewById(R.id.starts_in_hours); + TextView minutesTextView = (TextView) getActivity().findViewById(R.id.starts_in_minutes); - TextView daysTextViewLabel = (TextView) getActivity().findViewById( - R.id.starts_in_days_label); - TextView hoursTextViewLabel = (TextView) getActivity().findViewById( - R.id.starts_in_hours_label); - TextView minutesTextViewLabel = (TextView) getActivity().findViewById( - R.id.starts_in_minutes_label); + TextView daysTextViewLabel = (TextView) getActivity().findViewById(R.id.starts_in_days_label); + TextView hoursTextViewLabel = (TextView) getActivity().findViewById(R.id.starts_in_hours_label); + TextView minutesTextViewLabel = (TextView) getActivity().findViewById(R.id.starts_in_minutes_label); daysTextView.setText(String.format("%02d", days)); hoursTextView.setText(String.format("%02d", hours)); diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/SpeedFragment.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/SpeedFragment.java index ad548d7345a..1b690336b14 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/SpeedFragment.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/SpeedFragment.java @@ -11,22 +11,20 @@ import android.view.ViewGroup; import android.widget.TextView; import com.sap.sailing.android.tracking.app.R; +import com.sap.sailing.android.tracking.app.ui.activities.TrackingActivity; public class SpeedFragment extends BaseFragment { - private String TAG = SpeedFragment.class.getName(); - private final String SIS_SPEED_TEXTIVEW = "SpeedFragmentSpeedTextView"; + //private String TAG = SpeedFragment.class.getName(); public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); - ViewGroup view = (ViewGroup) inflater.inflate(R.layout.fragment_speed, container, false); + ViewGroup view = (ViewGroup) inflater.inflate(R.layout.fragment_speed, container, false); return view; } - public void setSpeed(float speedInMetersPerSecond) - { - if (isAdded()) - { + public void setSpeed(float speedInMetersPerSecond) { + if (isAdded()) { float speedInKnots = speedInMetersPerSecond * 1.9438444924574f; NumberFormat df = DecimalFormat.getInstance(); @@ -34,30 +32,29 @@ public class SpeedFragment extends BaseFragment { df.setMaximumFractionDigits(2); df.setRoundingMode(RoundingMode.HALF_UP); String formattedSpeed = df.format(speedInKnots); - + TextView speedText = (TextView) getActivity().findViewById(R.id.speed_text_view); speedText.setText(formattedSpeed + "kn"); + TrackingActivity activity = (TrackingActivity) getActivity(); + + if (activity != null) { + activity.lastSpeedIndicatorText = speedText.getText().toString(); + } } } - + @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - TextView speedText = (TextView)getActivity().findViewById(R.id.speed_text_view); - if (savedInstanceState != null) - { - speedText.setText(savedInstanceState.getString(SIS_SPEED_TEXTIVEW)); - } - else - { - speedText.setText("--kn"); + + TextView speedText = (TextView) getActivity().findViewById(R.id.speed_text_view); + + TrackingActivity activity = (TrackingActivity) getActivity(); + if (activity != null) { + speedText.setText(activity.lastSpeedIndicatorText); + } else { + speedText.setText(""); } + } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - TextView speedText = (TextView)getActivity().findViewById(R.id.speed_text_view); - outState.putString(SIS_SPEED_TEXTIVEW, speedText.getText().toString()); - } -} +} \ No newline at end of file diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/TrackingFragment.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/TrackingFragment.java index 6591c258642..8669ed34e85 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/TrackingFragment.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/TrackingFragment.java @@ -31,7 +31,6 @@ public class TrackingFragment extends BaseFragment { static final String SIS_GPS_ACCURACY = "instanceStateGpsAccuracy"; static final String SIS_GPS_UNSENT_FIXES = "instanceStateGpsUnsentFixes"; - private AppPreferences prefs; private long lastGPSQualityUpdate; @@ -247,11 +246,8 @@ public class TrackingFragment extends BaseFragment { { unsentGpsFixesTextView.setText(String.valueOf(count)); } - } }); } } - - } diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/DatabaseHelper.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/DatabaseHelper.java index 546fee4981e..388085368dd 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/DatabaseHelper.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/DatabaseHelper.java @@ -3,16 +3,20 @@ package com.sap.sailing.android.tracking.app.utils; import java.util.ArrayList; import java.util.List; +import android.content.ContentProviderOperation; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; +import android.content.OperationApplicationException; import android.database.Cursor; import android.net.Uri; +import android.os.RemoteException; import android.provider.BaseColumns; import com.sap.sailing.android.shared.logging.ExLog; import com.sap.sailing.android.tracking.app.BuildConfig; +import com.sap.sailing.android.tracking.app.provider.AnalyticsContract; import com.sap.sailing.android.tracking.app.provider.AnalyticsContract.Competitor; import com.sap.sailing.android.tracking.app.provider.AnalyticsContract.Event; import com.sap.sailing.android.tracking.app.provider.AnalyticsContract.EventGpsFixesJoined; @@ -195,18 +199,19 @@ public class DatabaseHelper { return result; } - public EventInfo getEventInfo(Context context, String eventId) { + public EventInfo getEventInfo(Context context, String eventId) { EventInfo event = new EventInfo(); event.id = eventId; Cursor cursor = context.getContentResolver().query(Event.CONTENT_URI, - null, "event_id = \"" + eventId + "\"", null, null); + null, "event_id = \"" + eventId + "\"", null, null); if (cursor.moveToFirst()) { event.name = cursor.getString(cursor.getColumnIndex(Event.EVENT_NAME)); event.imageUrl = cursor.getString(cursor.getColumnIndex(Event.EVENT_IMAGE_URL)); event.startMillis = cursor.getLong(cursor.getColumnIndex(Event.EVENT_DATE_START)); event.endMillis = cursor.getLong(cursor.getColumnIndex(Event.EVENT_DATE_END)); + event.server = cursor.getString(cursor.getColumnIndex(Event.EVENT_SERVER)); event.rowId = cursor.getInt(cursor.getColumnIndex(BaseColumns._ID)); } @@ -277,4 +282,107 @@ public class DatabaseHelper { ExLog.i(context, TAG, "Checkout, number of leaderbards deleted: " + d3); } } + + /** + * Attempt to check in to a race. + * + * @param context + * @param event + * @param competitor + * @param leaderboard + * @return success or failure + * @throws GeneralDatabaseHelperException + * @throws OperationApplicationException + * @throws RemoteException + */ + public void storeCheckinRow(Context context, EventInfo event, CompetitorInfo competitor, + LeaderboardInfo leaderboard) throws GeneralDatabaseHelperException { + + // inserting leaderboard first in order to get the ID. + // This should be atomic, but couldn't get withValueBackReference to + // work yet. + + ContentResolver cr = context.getContentResolver(); + + ContentValues clv = new ContentValues(); + clv.put(Leaderboard.LEADERBOARD_NAME, leaderboard.name); + cr.insert(Leaderboard.CONTENT_URI, clv); + + Cursor cur = cr.query(Leaderboard.CONTENT_URI, null, null, null, null); + long lastLeaderboardId = 0; + + if (cur.moveToLast()) { + lastLeaderboardId = cur.getLong(cur.getColumnIndex(BaseColumns._ID)); + } + + cur.close(); + + // now, with the leaderboard id, insert event and competitor + // todo: fix this so its atomic. + + ArrayList opList = new ArrayList(); + + ContentValues cev = new ContentValues(); + cev.put(Event.EVENT_ID, event.id); + cev.put(Event.EVENT_NAME, event.name); + cev.put(Event.EVENT_DATE_START, event.startMillis); + cev.put(Event.EVENT_DATE_END, event.endMillis); + cev.put(Event.EVENT_SERVER, event.server); + cev.put(Event.EVENT_IMAGE_URL, event.imageUrl); + cev.put(Event.EVENT_LEADERBOARD_FK, lastLeaderboardId); + + opList.add(ContentProviderOperation.newInsert(Event.CONTENT_URI).withValues(cev).build()); + + ContentValues ccv = new ContentValues(); + + ccv.put(Competitor.COMPETITOR_COUNTRY_CODE, competitor.countryCode); + ccv.put(Competitor.COMPETITOR_DISPLAY_NAME, competitor.name); + ccv.put(Competitor.COMPETITOR_ID, competitor.id); + ccv.put(Competitor.COMPETITOR_NATIONALITY, competitor.nationality); + ccv.put(Competitor.COMPETITOR_SAIL_ID, competitor.sailId); + ccv.put(Competitor.COMPETITOR_LEADERBOARD_FK, lastLeaderboardId); + + opList.add(ContentProviderOperation.newInsert(Competitor.CONTENT_URI).withValues(ccv).build()); + + try { + cr.applyBatch(AnalyticsContract.CONTENT_AUTHORITY, opList); + } catch (RemoteException e) { + throw new GeneralDatabaseHelperException(e.getMessage()); + } catch (OperationApplicationException e) { + throw new GeneralDatabaseHelperException(e.getMessage()); + } + } + + /** + * Return true if the combination of eventId, leaderboardName and + * competitorId does not exist in the DB. + * + * @param eventId + * @param leaderboardName + * @param competitorId + * @return + */ + public boolean eventLeaderboardCompetitorCombnationAvailable(Context context, + String eventId, String leaderboardName, String competitorId) { + + ContentResolver cr = context.getContentResolver(); + String sel = "leaderboards.leaderboard_name = \"" + leaderboardName + + "\" AND competitors.competitor_id = \"" + competitorId + + "\" AND events.event_id = \"" + eventId + "\""; + + int count = cr.query( + AnalyticsContract.EventLeaderboardCompetitorJoined.CONTENT_URI, + null, sel, null, null).getCount(); + + return count == 0; + } + + public class GeneralDatabaseHelperException extends Exception { + private static final long serialVersionUID = 4333494334720305541L; + + public GeneralDatabaseHelperException(String message) { + super(message); + } + } + } diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/ImageController.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/ImageController.java new file mode 100644 index 00000000000..b4e41f7e717 --- /dev/null +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/ImageController.java @@ -0,0 +1,495 @@ +package com.sap.sailing.android.tracking.app.utils; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.RectF; +import android.media.ExifInterface; +import android.net.Uri; +import android.provider.MediaStore; +import android.util.Base64; + +/** + * Todo: Use this class? + * + */ +public class ImageController { + + public static String getRealPathFromURI(ContentResolver contentResolver, + Uri contentUri) { + String res = null; + String[] proj = { MediaStore.Images.Media.DATA }; + Cursor cursor = contentResolver.query(contentUri, proj, null, null, + null); + if (cursor.moveToFirst()) { + int column_index = cursor + .getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + res = cursor.getString(column_index); + } + cursor.close(); + return res; + } + + // ----------------------------------------------------------------------- + + public static Bitmap scaleCenterCrop(Bitmap source, int newWidth, + int newHeight) { + int sourceWidth = source.getWidth(); + int sourceHeight = source.getHeight(); + + // Compute the scaling factors to fit the new height and width, + // respectively. + // To cover the final image, the final scaling will be the bigger + // of these two. + float xScale = (float) newWidth / sourceWidth; + float yScale = (float) newHeight / sourceHeight; + float scale = Math.max(xScale, yScale); + + // Now get the size of the source bitmap when scaled + float scaledWidth = scale * sourceWidth; + float scaledHeight = scale * sourceHeight; + + // Let's find out the upper left coordinates if the scaled bitmap + // should be centered in the new size give by the parameters + float left = (newWidth - scaledWidth) / 2; + float top = (newHeight - scaledHeight) / 2; + + // The target rectangle for the new, scaled version of the source bitmap + // will now + // be + RectF targetRect = new RectF(left, top, left + scaledWidth, top + + scaledHeight); + + // Finally, we create a new bitmap of the specified size and draw our + // new, + // scaled bitmap onto it. + Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, + source.getConfig()); + Canvas canvas = new Canvas(dest); + canvas.drawBitmap(source, null, targetRect, null); + + return dest; + } + + public static Bitmap scaleBitmap(Bitmap bitmap, int newWidth, int newHeight) { + Bitmap scaledBitmap = Bitmap.createBitmap(newWidth, newHeight, + Config.ARGB_8888); + + float ratioX = newWidth / (float) bitmap.getWidth(); + float ratioY = newHeight / (float) bitmap.getHeight(); + float middleX = newWidth / 2.0f; + float middleY = newHeight / 2.0f; + + Matrix scaleMatrix = new Matrix(); + scaleMatrix.setScale(ratioX, ratioY, middleX, middleY); + + Canvas canvas = new Canvas(scaledBitmap); + canvas.setMatrix(scaleMatrix); + float left = middleX - (float) bitmap.getWidth() / 2; + float top = middleY - (float) bitmap.getHeight() / 2; + canvas.drawBitmap(bitmap, left, top, + new Paint(Paint.FILTER_BITMAP_FLAG)); + return scaledBitmap; + } + + public static Bitmap scaleBitmapAspectRatio(Bitmap bitmap, int targetWidth, + int targetHeight) { + float ratioX = targetWidth / (float) bitmap.getWidth(); + float ratioY = targetHeight / (float) bitmap.getHeight(); + float aspectRatio = (float) bitmap.getWidth() / bitmap.getHeight(); + if (ratioX < ratioY) { + aspectRatio = ratioX; + } else { + aspectRatio = ratioY; + } + int newWidth = Math.round(bitmap.getWidth() * aspectRatio); + if (newWidth < 1) { + newWidth = 1; + } + int newHeight = Math.round(bitmap.getHeight() * aspectRatio); + if (newHeight < 1) { + newHeight = 1; + } + return scaleBitmap(bitmap, newWidth, newHeight); + } + + // ----------------------------------------------------------------------- + + // http://stackoverflow.com/a/10028267 + public static Bitmap fastblur(Bitmap sentBitmap, int radius) { + + // Stack Blur v1.0 from + // http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html + // + // Java Author: Mario Klingemann + // http://incubator.quasimondo.com + // created Feburary 29, 2004 + // Android port : Yahel Bouaziz + // http://www.kayenko.com + // ported april 5th, 2012 + + // This is a compromise between Gaussian Blur and Box blur + // It creates much better looking blurs than Box Blur, but is + // 7x faster than my Gaussian Blur implementation. + // + // I called it Stack Blur because this describes best how this + // filter works internally: it creates a kind of moving stack + // of colors whilst scanning through the image. Thereby it + // just has to add one new block of color to the right side + // of the stack and remove the leftmost color. The remaining + // colors on the topmost layer of the stack are either added on + // or reduced by one, depending on if they are on the right or + // on the left side of the stack. + // + // If you are using this algorithm in your code please add + // the following line: + // + // Stack Blur Algorithm by Mario Klingemann + + Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig(), true); + + if (radius < 1) { + return (null); + } + + int w = bitmap.getWidth(); + int h = bitmap.getHeight(); + + int[] pix = new int[w * h]; + bitmap.getPixels(pix, 0, w, 0, 0, w, h); + + int wm = w - 1; + int hm = h - 1; + int wh = w * h; + int div = radius + radius + 1; + + int r[] = new int[wh]; + int g[] = new int[wh]; + int b[] = new int[wh]; + int rsum, gsum, bsum, x, y, i, p, yp, yi, yw; + int vmin[] = new int[Math.max(w, h)]; + + int divsum = (div + 1) >> 1; + divsum *= divsum; + int dv[] = new int[256 * divsum]; + for (i = 0; i < 256 * divsum; i++) { + dv[i] = (i / divsum); + } + + yw = yi = 0; + + int[][] stack = new int[div][3]; + int stackpointer; + int stackstart; + int[] sir; + int rbs; + int r1 = radius + 1; + int routsum, goutsum, boutsum; + int rinsum, ginsum, binsum; + + for (y = 0; y < h; y++) { + rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; + for (i = -radius; i <= radius; i++) { + p = pix[yi + Math.min(wm, Math.max(i, 0))]; + sir = stack[i + radius]; + sir[0] = (p & 0xff0000) >> 16; + sir[1] = (p & 0x00ff00) >> 8; + sir[2] = (p & 0x0000ff); + rbs = r1 - Math.abs(i); + rsum += sir[0] * rbs; + gsum += sir[1] * rbs; + bsum += sir[2] * rbs; + if (i > 0) { + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + } else { + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + } + } + stackpointer = radius; + + for (x = 0; x < w; x++) { + + r[yi] = dv[rsum]; + g[yi] = dv[gsum]; + b[yi] = dv[bsum]; + + rsum -= routsum; + gsum -= goutsum; + bsum -= boutsum; + + stackstart = stackpointer - radius + div; + sir = stack[stackstart % div]; + + routsum -= sir[0]; + goutsum -= sir[1]; + boutsum -= sir[2]; + + if (y == 0) { + vmin[x] = Math.min(x + radius + 1, wm); + } + p = pix[yw + vmin[x]]; + + sir[0] = (p & 0xff0000) >> 16; + sir[1] = (p & 0x00ff00) >> 8; + sir[2] = (p & 0x0000ff); + + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + + rsum += rinsum; + gsum += ginsum; + bsum += binsum; + + stackpointer = (stackpointer + 1) % div; + sir = stack[(stackpointer) % div]; + + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + + rinsum -= sir[0]; + ginsum -= sir[1]; + binsum -= sir[2]; + + yi++; + } + yw += w; + } + for (x = 0; x < w; x++) { + rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; + yp = -radius * w; + for (i = -radius; i <= radius; i++) { + yi = Math.max(0, yp) + x; + + sir = stack[i + radius]; + + sir[0] = r[yi]; + sir[1] = g[yi]; + sir[2] = b[yi]; + + rbs = r1 - Math.abs(i); + + rsum += r[yi] * rbs; + gsum += g[yi] * rbs; + bsum += b[yi] * rbs; + + if (i > 0) { + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + } else { + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + } + + if (i < hm) { + yp += w; + } + } + yi = x; + stackpointer = radius; + for (y = 0; y < h; y++) { + // Preserve alpha channel: ( 0xff000000 & pix[yi] ) + pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) + | (dv[gsum] << 8) | dv[bsum]; + + rsum -= routsum; + gsum -= goutsum; + bsum -= boutsum; + + stackstart = stackpointer - radius + div; + sir = stack[stackstart % div]; + + routsum -= sir[0]; + goutsum -= sir[1]; + boutsum -= sir[2]; + + if (x == 0) { + vmin[y] = Math.min(y + r1, hm) * w; + } + p = x + vmin[y]; + + sir[0] = r[p]; + sir[1] = g[p]; + sir[2] = b[p]; + + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + + rsum += rinsum; + gsum += ginsum; + bsum += binsum; + + stackpointer = (stackpointer + 1) % div; + sir = stack[stackpointer]; + + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + + rinsum -= sir[0]; + ginsum -= sir[1]; + binsum -= sir[2]; + + yi += w; + } + } + + bitmap.setPixels(pix, 0, w, 0, 0, w, h); + + return (bitmap); + } + + // ----------------------------------------------------------------------- + + // http://developer.android.com/training/displaying-bitmaps/load-bitmap.html + public static int calculateInSampleSize(BitmapFactory.Options options, + int reqWidth, int reqHeight) { + // Raw height and width of image + final int height = options.outHeight; + final int width = options.outWidth; + int inSampleSize = 1; + + if (height > reqHeight || width > reqWidth) { + + final int halfHeight = height / 2; + final int halfWidth = width / 2; + + // Calculate the largest inSampleSize value that is a power of 2 and + // keeps both + // height and width larger than the requested height and width. + while ((halfHeight / inSampleSize) > reqHeight + && (halfWidth / inSampleSize) > reqWidth) { + inSampleSize *= 2; + } + } + return inSampleSize; + } + + public static Bitmap decodeSampledBitmapFromFile( + ContentResolver contentResolver, Uri uri, String filename, + int reqWidth, int reqHeight) { + + // First decode with inJustDecodeBounds=true to check dimensions + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + if (filename == null) { + try { + InputStream inputStream = contentResolver.openInputStream(uri); + BitmapFactory.decodeStream(inputStream, null, options); + } catch (IOException e) { + + } + } else { + BitmapFactory.decodeFile(filename, options); + + } + // Calculate inSampleSize + options.inSampleSize = calculateInSampleSize(options, reqWidth, + reqHeight); + + // Decode bitmap with inSampleSize set + options.inJustDecodeBounds = false; + Bitmap bitmap = null; + if (filename == null) { + try { + InputStream inputStream = contentResolver.openInputStream(uri); + bitmap = BitmapFactory.decodeStream(inputStream, null, options); + } catch (IOException e) { + + } + } else { + bitmap = BitmapFactory.decodeFile(filename, options); + } + + // check orientation + if (bitmap != null && filename != null) { + try { + ExifInterface exif = new ExifInterface(filename); + int orientation = exif.getAttributeInt( + ExifInterface.TAG_ORIENTATION, 1); + int angle = 0; + + switch (orientation) { + case ExifInterface.ORIENTATION_ROTATE_90: + angle = -90; + break; + case ExifInterface.ORIENTATION_ROTATE_180: + angle = -180; + break; + case ExifInterface.ORIENTATION_ROTATE_270: + angle = -270; + break; + + default: + break; + } + if (angle != 0) { + Matrix matrix = new Matrix(); + matrix.postRotate(90); + Bitmap rotatedBitmap = Bitmap + .createBitmap(bitmap, 0, 0, bitmap.getWidth(), + bitmap.getHeight(), matrix, true); + bitmap.recycle(); + bitmap = rotatedBitmap; + } + } catch (IOException e) { + + } + } + return bitmap; + } + + // ----------------------------------------------------------------------- + + // http://stackoverflow.com/a/8557147 + private final static String[] projection = new String[] { + MediaStore.Images.ImageColumns._ID, + MediaStore.Images.ImageColumns.DATA, + MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME, + MediaStore.Images.ImageColumns.DATE_TAKEN, + MediaStore.Images.ImageColumns.MIME_TYPE }; + + public static File latestImage(Context context) { + final Cursor cursor = context.getContentResolver().query( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, null, + null, MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC"); + + // Put it in the image view + if (cursor.moveToFirst()) { + String imageLocation = cursor.getString(1); + File imageFile = new File(imageLocation); + return imageFile; + } + return null; + } + + // ----------------------------------------------------------------------- + + public static String encodeTobase64(Bitmap image) { + Bitmap immagex = image; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + immagex.compress(Bitmap.CompressFormat.JPEG, 60, baos); + byte[] b = baos.toByteArray(); + String imageEncoded = Base64.encodeToString(b, Base64.NO_WRAP); + return "data:image/jpeg;base64," + imageEncoded; + } +} diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/valueobjects/CheckinData.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/valueobjects/CheckinData.java new file mode 100644 index 00000000000..151851840c6 --- /dev/null +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/valueobjects/CheckinData.java @@ -0,0 +1,49 @@ +package com.sap.sailing.android.tracking.app.valueobjects; + +public class CheckinData { + // public String gcmId; + public String leaderboardName; + public String eventId; + public String eventName; + public String eventStartDateStr; + public String eventEndDateStr; + public String eventFirstImageUrl; + public String eventServerUrl; + public String checkinURL; + public String competitorName; + public String competitorId; + public String competitorSailId; + public String competitorNationality; + public String competitorCountryCode; + public String deviceUid; + + public EventInfo getEvent() + { + EventInfo event = new EventInfo(); + event.name = eventName; + event.id = eventId; + event.startMillis = Long.parseLong(eventStartDateStr); + event.endMillis = Long.parseLong(eventEndDateStr); + event.imageUrl = eventFirstImageUrl; + event.server = eventServerUrl; + return event; + } + + public LeaderboardInfo getLeaderboard() + { + LeaderboardInfo leaderboard = new LeaderboardInfo(); + leaderboard.name = leaderboardName; + return leaderboard; + } + + public CompetitorInfo getCompetitor() + { + CompetitorInfo competitor = new CompetitorInfo(); + competitor.name = competitorName; + competitor.id = competitorId; + competitor.sailId = competitorSailId; + competitor.nationality = competitorNationality; + competitor.countryCode = competitorCountryCode; + return competitor; + } +} \ No newline at end of file diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/valueobjects/CompetitorInfo.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/valueobjects/CompetitorInfo.java index 709ccaf365f..f821bc2d9f4 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/valueobjects/CompetitorInfo.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/valueobjects/CompetitorInfo.java @@ -5,6 +5,7 @@ public class CompetitorInfo { public String id; public String name; public String countryCode; + public String nationality; public String sailId; public int rowId; diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/valueobjects/EventInfo.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/valueobjects/EventInfo.java index 4cad3d70505..e5dd9242f09 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/valueobjects/EventInfo.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/valueobjects/EventInfo.java @@ -9,6 +9,7 @@ public class EventInfo { public long startMillis; public long endMillis; public int rowId; + public String server; @Override public String toString() { @@ -16,6 +17,6 @@ public class EventInfo { + leaderboardName + ", eventImageUrl: " + imageUrl + ", eventStartMillis: " + startMillis + ", eventEndMillis: " + endMillis + ", eventRowId: " - + rowId; + + rowId + ", server: " + server; } }