diff --git a/src/androidTest/java/de/dennisguse/opentracks/content/DescriptionGeneratorTest.java b/src/androidTest/java/de/dennisguse/opentracks/share/DescriptionGeneratorTest.java similarity index 88% rename from src/androidTest/java/de/dennisguse/opentracks/content/DescriptionGeneratorTest.java rename to src/androidTest/java/de/dennisguse/opentracks/share/DescriptionGeneratorTest.java index 613f52bd7..eaa0193c2 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/content/DescriptionGeneratorTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/share/DescriptionGeneratorTest.java @@ -14,7 +14,7 @@ * the License. */ -package de.dennisguse.opentracks.content; +package de.dennisguse.opentracks.share; import static org.junit.Assert.assertEquals; @@ -82,18 +82,18 @@ public class DescriptionGeneratorTest { + "Description: -
" + "Total distance: 20.00 km (12.4 mi)
" + "Total time: 10:00
" - + "Moving time: 05:00
" - + "Average speed: 120.00 km/h (74.6 mi/h)
" - + "Average moving speed: 240.00 km/h (149.1 mi/h)
" - + "Max speed: 360.00 km/h (223.7 mi/h)
" - + "Average pace: 0:30 min/km (0:48 min/mi)
" - + "Average moving pace: 0:15 min/km (0:24 min/mi)
" - + "Fastest pace: 0:10 min/km (0:16 min/mi)
" - + "Max elevation: 550 m (1804 ft)
" - + "Min elevation: -500 m (-1640 ft)
" - + "Elevation gain: 6000 m (19685 ft)
" - + "Elevation loss: 6000 m (19685 ft)
" - + "Recorded: " + StringUtils.formatDateTime(OffsetDateTime.ofInstant(START_TIME, ZoneId.systemDefault())) + "
"; + + "Moving time: 05:00
" + + "Average speed: 120.00 km/h (74.6 mi/h)
" + + "Average moving speed: 240.00 km/h (149.1 mi/h)
" + + "Max speed: 360.00 km/h (223.7 mi/h)
" + + "Average pace: 0:30 min/km (0:48 min/mi)
" + + "Average moving pace: 0:15 min/km (0:24 min/mi)
" + + "Fastest pace: 0:10 min/km (0:16 min/mi)
" + + "Max elevation: 550 m (1804 ft)
" + + "Min elevation: -500 m (-1640 ft)
" + + "Elevation gain: 6000 m (19685 ft)
" + + "Elevation loss: 6000 m (19685 ft)
" + + "Recorded: " + StringUtils.formatDateTime(OffsetDateTime.ofInstant(START_TIME, ZoneId.systemDefault())) + "
"; assertEquals(expected, descriptionGenerator.generateTrackDescription(track, true)); } diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 4562ad8bd..470c29c6f 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -328,7 +328,7 @@ limitations under the License. @@ -336,7 +336,7 @@ limitations under the License. diff --git a/src/main/java/de/dennisguse/opentracks/TrackListActivity.java b/src/main/java/de/dennisguse/opentracks/TrackListActivity.java index 64a235bd7..21d1d721c 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackListActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackListActivity.java @@ -63,6 +63,7 @@ import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.handlers.GpsStatusValue; import de.dennisguse.opentracks.settings.PreferencesUtils; import de.dennisguse.opentracks.settings.SettingsActivity; +import de.dennisguse.opentracks.share.ShareUtils; import de.dennisguse.opentracks.ui.aggregatedStatistics.AggregatedStatisticsActivity; import de.dennisguse.opentracks.ui.aggregatedStatistics.ConfirmDeleteDialogFragment; import de.dennisguse.opentracks.ui.markers.MarkerListActivity; @@ -449,7 +450,7 @@ public class TrackListActivity extends AbstractTrackDeleteActivity implements Co } if (itemId == R.id.list_context_menu_share) { - Intent intent = IntentUtils.newShareFileIntent(this, trackIds); + Intent intent = ShareUtils.newShareFileIntent(this, trackIds); intent = Intent.createChooser(intent, null); startActivity(intent); return true; diff --git a/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java b/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java index 6f2063a32..79add8a67 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java @@ -45,6 +45,7 @@ import de.dennisguse.opentracks.services.TrackRecordingService; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.settings.PreferencesUtils; import de.dennisguse.opentracks.settings.SettingsActivity; +import de.dennisguse.opentracks.share.ShareUtils; import de.dennisguse.opentracks.ui.aggregatedStatistics.ConfirmDeleteDialogFragment; import de.dennisguse.opentracks.ui.intervals.IntervalsFragment; import de.dennisguse.opentracks.ui.markers.MarkerListActivity; @@ -182,7 +183,7 @@ public class TrackRecordedActivity extends AbstractTrackDeleteActivity implement @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == R.id.track_detail_share) { - Intent intent = Intent.createChooser(IntentUtils.newShareFileIntent(this, trackId), null); + Intent intent = Intent.createChooser(ShareUtils.newShareFileIntent(this, trackId), null); startActivity(intent); return true; } diff --git a/src/main/java/de/dennisguse/opentracks/content/DescriptionGenerator.java b/src/main/java/de/dennisguse/opentracks/share/DescriptionGenerator.java similarity index 98% rename from src/main/java/de/dennisguse/opentracks/content/DescriptionGenerator.java rename to src/main/java/de/dennisguse/opentracks/share/DescriptionGenerator.java index a65f2bb26..5f16f396b 100644 --- a/src/main/java/de/dennisguse/opentracks/content/DescriptionGenerator.java +++ b/src/main/java/de/dennisguse/opentracks/share/DescriptionGenerator.java @@ -14,7 +14,7 @@ * the License. */ -package de.dennisguse.opentracks.content; +package de.dennisguse.opentracks.share; import android.content.Context; import android.util.Pair; @@ -54,8 +54,8 @@ public class DescriptionGenerator { /** * Generates a track description. * - * @param track the track - * @param html true to output html, false to output plain text + * @param track the track + * @param html true to output html, false to output plain text */ public String generateTrackDescription(Track track, boolean html) { String paragraphSeparator = html ? HTML_PARAGRAPH_SEPARATOR : TEXT_PARAGRAPH_SEPARATOR; diff --git a/src/main/java/de/dennisguse/opentracks/share/ShareUtils.java b/src/main/java/de/dennisguse/opentracks/share/ShareUtils.java new file mode 100644 index 000000000..97729262c --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/share/ShareUtils.java @@ -0,0 +1,124 @@ +package de.dennisguse.opentracks.share; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.util.Log; +import android.util.Pair; + +import androidx.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; + +import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.data.ContentProviderUtils; +import de.dennisguse.opentracks.data.ShareContentProvider; +import de.dennisguse.opentracks.data.models.Marker; +import de.dennisguse.opentracks.data.models.Track; +import de.dennisguse.opentracks.io.file.TrackFileFormat; +import de.dennisguse.opentracks.settings.PreferencesUtils; +import de.dennisguse.opentracks.util.FileUtils; + +public class ShareUtils { + + private static final String TAG = ShareUtils.class.getSimpleName(); + + private ShareUtils() { + } + + + /** + * Creates an intent to share a track file with an app. + * + * @param context the context + * @param trackIds the track ids + */ + public static Intent newShareFileIntent(Context context, Track.Id... trackIds) { + if (trackIds.length == 0) { + throw new RuntimeException("Need to share at least one track."); + } + + ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context); + + String trackDescription = ""; + if (trackIds.length == 1) { + Track track = contentProviderUtils.getTrack(trackIds[0]); + trackDescription = track == null ? "" : new DescriptionGenerator(context).generateTrackDescription(track, false); + } + + ArrayList uris = new ArrayList<>(); + for (Track.Id trackId : trackIds) { + Track track = contentProviderUtils.getTrack(trackId); + if (track == null) { + Log.e(TAG, "TrackId " + trackId.getId() + " could not be resolved."); + continue; + } + + String trackName = FileUtils.sanitizeFileName(track.getName()); + Pair uriTrackFile = ShareContentProvider.createURI(trackId, trackName, PreferencesUtils.getExportTrackFileFormat()); + Pair uriSharePicture = ShareContentProvider.createURI(trackId, trackName, TrackFileFormat.SHARE_PICTURE_PNG); + + uris.addAll(Arrays.asList(uriSharePicture.first, uriTrackFile.first)); + } + + return new Intent(Intent.ACTION_SEND_MULTIPLE) + .setType("image/*") + .putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + .putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.share_track_subject)) + .putExtra(Intent.EXTRA_TEXT, context.getString(R.string.share_track_share_file_body, trackDescription)); + } + + /** + * Creates an intent to share a track file with an app. + * + * @param context the context + * @param markerIds the marker ids + * @return an Intent or null (if nothing can be shared). + */ + @Nullable + public static Intent newShareFileIntent(Context context, Marker.Id... markerIds) { + if (markerIds.length == 0) { + throw new RuntimeException("Need to share at least one marker."); + } + + String mime = null; + + ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context); + ArrayList uris = new ArrayList<>(); + for (Marker.Id markerId : markerIds) { + Marker marker = contentProviderUtils.getMarker(markerId); + if (marker == null) { + Log.e(TAG, "MarkerId " + markerId.getId() + " could not be resolved."); + continue; + } + if (marker.getPhotoURI() == null) { + Log.e(TAG, "MarkerId " + markerId.getId() + " has no picture."); + continue; + } + + mime = context.getContentResolver().getType(marker.getPhotoURI()); + + uris.add(marker.getPhotoURI()); + } + + if (uris.isEmpty()) { + return null; + } + + /* + * Because the #166 bug, when you import KMZ tracks then it creates file:/// from markers with photo. + * The photos should be content:/// not file:/// because getType(uri) always returns null for file:/// + * In .setType, to avoid side effects because the #166 bug described above it checks if mime is null. + * If it is then it hardcode "images/*". + */ + return new Intent(Intent.ACTION_SEND_MULTIPLE) + .setType(mime != null ? mime : "image/*") + .putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + .putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.share_image_subject)) + .putExtra(Intent.EXTRA_TEXT, context.getString(R.string.share_image_body)); + } + +} diff --git a/src/main/java/de/dennisguse/opentracks/ShowOnMapProxyActivity.java b/src/main/java/de/dennisguse/opentracks/share/ShowOnMapProxyActivity.java similarity index 96% rename from src/main/java/de/dennisguse/opentracks/ShowOnMapProxyActivity.java rename to src/main/java/de/dennisguse/opentracks/share/ShowOnMapProxyActivity.java index c68bf91a5..efd33d93a 100644 --- a/src/main/java/de/dennisguse/opentracks/ShowOnMapProxyActivity.java +++ b/src/main/java/de/dennisguse/opentracks/share/ShowOnMapProxyActivity.java @@ -1,4 +1,4 @@ -package de.dennisguse.opentracks; +package de.dennisguse.opentracks.share; import android.content.Context; import android.content.Intent; @@ -10,6 +10,7 @@ import androidx.appcompat.app.AppCompatActivity; import java.util.Set; +import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.data.ShareContentProvider; import de.dennisguse.opentracks.data.models.Track; import de.dennisguse.opentracks.io.file.TrackFileFormat; diff --git a/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerDetailFragment.java b/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerDetailFragment.java index 229938775..66d99cfed 100644 --- a/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerDetailFragment.java +++ b/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerDetailFragment.java @@ -40,6 +40,7 @@ import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.data.ContentProviderUtils; import de.dennisguse.opentracks.data.models.Marker; import de.dennisguse.opentracks.databinding.MarkerDetailFragmentBinding; +import de.dennisguse.opentracks.share.ShareUtils; import de.dennisguse.opentracks.util.IntentUtils; import de.dennisguse.opentracks.util.ListItemUtils; import de.dennisguse.opentracks.util.StringUtils; @@ -205,7 +206,7 @@ public class MarkerDetailFragment extends Fragment { if (item.getItemId() == R.id.marker_detail_share) { if (marker.hasPhoto()) { - Intent intent = IntentUtils.newShareFileIntent(getContext(), marker.getId()); + Intent intent = ShareUtils.newShareFileIntent(getContext(), marker.getId()); intent = Intent.createChooser(intent, null); startActivity(intent); } diff --git a/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerListActivity.java b/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerListActivity.java index a25b48bd1..9bc131321 100644 --- a/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerListActivity.java +++ b/src/main/java/de/dennisguse/opentracks/ui/markers/MarkerListActivity.java @@ -40,6 +40,7 @@ import de.dennisguse.opentracks.data.tables.MarkerColumns; import de.dennisguse.opentracks.databinding.MarkerListBinding; import de.dennisguse.opentracks.services.TrackRecordingService; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; +import de.dennisguse.opentracks.share.ShareUtils; import de.dennisguse.opentracks.ui.markers.DeleteMarkerDialogFragment.DeleteMarkerCaller; import de.dennisguse.opentracks.ui.util.ScrollVisibleViews; import de.dennisguse.opentracks.util.ActivityUtils; @@ -206,7 +207,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker } if (itemId == R.id.list_context_menu_share) { - Intent intent = IntentUtils.newShareFileIntent(this, markerIds); + Intent intent = ShareUtils.newShareFileIntent(this, markerIds); if (intent != null) { intent = Intent.createChooser(intent, null); startActivity(intent); diff --git a/src/main/java/de/dennisguse/opentracks/util/IntentUtils.java b/src/main/java/de/dennisguse/opentracks/util/IntentUtils.java index 541dbaf39..d9c326e24 100644 --- a/src/main/java/de/dennisguse/opentracks/util/IntentUtils.java +++ b/src/main/java/de/dennisguse/opentracks/util/IntentUtils.java @@ -24,24 +24,15 @@ import android.provider.MediaStore; import android.util.Log; import android.util.Pair; -import androidx.annotation.Nullable; import androidx.core.content.FileProvider; import androidx.documentfile.provider.DocumentFile; import java.io.File; import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; -import de.dennisguse.opentracks.R; -import de.dennisguse.opentracks.content.DescriptionGenerator; -import de.dennisguse.opentracks.data.ContentProviderUtils; -import de.dennisguse.opentracks.data.ShareContentProvider; import de.dennisguse.opentracks.data.models.Marker; import de.dennisguse.opentracks.data.models.Track; -import de.dennisguse.opentracks.io.file.TrackFileFormat; -import de.dennisguse.opentracks.settings.PreferencesUtils; /** * Utilities for creating intents. @@ -67,99 +58,6 @@ public class IntentUtils { return new Intent(context, cls).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); } - /** - * Creates an intent to share a track file with an app. - * - * @param context the context - * @param trackIds the track ids - */ - public static Intent newShareFileIntent(Context context, Track.Id... trackIds) { - if (trackIds.length == 0) { - throw new RuntimeException("Need to share at least one track."); - } - - ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context); - - String trackDescription = ""; - if (trackIds.length == 1) { - Track track = contentProviderUtils.getTrack(trackIds[0]); - trackDescription = track == null ? "" : new DescriptionGenerator(context).generateTrackDescription(track, false); - } - - ArrayList uris = new ArrayList<>(); - for (Track.Id trackId : trackIds) { - Track track = contentProviderUtils.getTrack(trackId); - if (track == null) { - Log.e(TAG, "TrackId " + trackId.getId() + " could not be resolved."); - continue; - } - - String trackName = FileUtils.sanitizeFileName(track.getName()); - Pair uriTrackFile = ShareContentProvider.createURI(trackId, trackName, PreferencesUtils.getExportTrackFileFormat()); - Pair uriSharePicture = ShareContentProvider.createURI(trackId, trackName, TrackFileFormat.SHARE_PICTURE_PNG); - - uris.addAll(Arrays.asList(uriSharePicture.first, uriTrackFile.first)); - } - - return new Intent(Intent.ACTION_SEND_MULTIPLE) - .setType("image/*") - .putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris) - .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - .putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.share_track_subject)) - .putExtra(Intent.EXTRA_TEXT, context.getString(R.string.share_track_share_file_body, trackDescription)); - } - - /** - * Creates an intent to share a track file with an app. - * - * @param context the context - * @param markerIds the marker ids - * @return an Intent or null (if nothing can be shared). - */ - @Nullable - public static Intent newShareFileIntent(Context context, Marker.Id... markerIds) { - if (markerIds.length == 0) { - throw new RuntimeException("Need to share at least one marker."); - } - - String mime = null; - - ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context); - ArrayList uris = new ArrayList<>(); - for (Marker.Id markerId : markerIds) { - Marker marker = contentProviderUtils.getMarker(markerId); - if (marker == null) { - Log.e(TAG, "MarkerId " + markerId.getId() + " could not be resolved."); - continue; - } - if (marker.getPhotoURI() == null) { - Log.e(TAG, "MarkerId " + markerId.getId() + " has no picture."); - continue; - } - - mime = context.getContentResolver().getType(marker.getPhotoURI()); - - uris.add(marker.getPhotoURI()); - } - - if (uris.isEmpty()) { - return null; - } - - /* - * Because the #166 bug, when you import KMZ tracks then it creates file:/// from markers with photo. - * The photos should be content:/// not file:/// because getType(uri) always returns null for file:/// - * In .setType, to avoid side effects because the #166 bug described above it checks if mime is null. - * If it is then it hardcode "images/*". - */ - return new Intent(Intent.ACTION_SEND_MULTIPLE) - .setType(mime != null ? mime : "image/*") - .putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris) - .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - .putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.share_image_subject)) - .putExtra(Intent.EXTRA_TEXT, context.getString(R.string.share_image_body)); - } - public static void showCoordinateOnMap(Context context, Marker marker) { showCoordinateOnMap(context, marker.getLatitude(), marker.getLongitude(), marker.getName()); }