From fc0cec6157b184209ee310ae3b06e966a83faa63 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Wed, 21 Jul 2021 18:17:36 +0200 Subject: [PATCH] Share picture: if sharing a track, a share picture (PNG) is created in addition. Fixes #363. --- .idea/runConfigurations.xml | 13 --- .../provider/ShareContentProvider.java | 29 ++++-- .../opentracks/io/file/TrackFileFormat.java | 19 ++++ .../exporter/SharePictureTrackExporter.java | 88 +++++++++++++++++++ .../opentracks/settings/SettingsActivity.java | 7 +- .../opentracks/util/IntentUtils.java | 14 +-- src/main/res/layout/track_share_picture.xml | 87 ++++++++++++++++++ 7 files changed, 231 insertions(+), 26 deletions(-) delete mode 100644 .idea/runConfigurations.xml create mode 100644 src/main/java/de/dennisguse/opentracks/io/file/exporter/SharePictureTrackExporter.java create mode 100644 src/main/res/layout/track_share_picture.xml diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index e497da999..000000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/content/provider/ShareContentProvider.java b/src/main/java/de/dennisguse/opentracks/content/provider/ShareContentProvider.java index bf3a6bb66..a38120234 100644 --- a/src/main/java/de/dennisguse/opentracks/content/provider/ShareContentProvider.java +++ b/src/main/java/de/dennisguse/opentracks/content/provider/ShareContentProvider.java @@ -54,6 +54,8 @@ public class ShareContentProvider extends CustomContentProvider { private static final int URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA = 6; private static final int URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES = 7; + private static final int URI_SHARE_PICTURE = 8; + private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); private static final String TRACKID_DELIMITER = "_"; @@ -64,14 +66,22 @@ public class ShareContentProvider extends CustomContentProvider { uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA.getName() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_AND_SENSORDATA); uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getName() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES); + + uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.SHARE_PICTURE_PNG.getName() + "/*/*", URI_SHARE_PICTURE); } + /** + * @return An URI for one file containing this tracks. + */ public static Pair createURI(Track.Id trackId, String trackName, @NonNull TrackFileFormat trackFileFormat) { Set trackIds = new HashSet<>(1); trackIds.add(trackId); return createURI(trackIds, trackName, trackFileFormat); } + /** + * @return An URI for one file containing all tracks. + */ public static Pair createURI(Set trackIds, String trackName, @NonNull TrackFileFormat trackFileFormat) { if (trackIds.isEmpty()) { throw new UnsupportedOperationException(); @@ -138,6 +148,9 @@ public class ShareContentProvider extends CustomContentProvider { case URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES: return TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES; + case URI_SHARE_PICTURE: + return TrackFileFormat.SHARE_PICTURE_PNG; + default: throw new RuntimeException("Could not derive TrackFileFormat from Uri " + uri); } @@ -163,12 +176,15 @@ public class ShareContentProvider extends CustomContentProvider { Object[] values = new Object[projection.length]; int i = 0; for (String col : projection) { - if (OpenableColumns.DISPLAY_NAME.equals(col)) { - cols[i] = OpenableColumns.DISPLAY_NAME; - values[i++] = uri.getLastPathSegment(); - } else if (OpenableColumns.SIZE.equals(col)) { - cols[i] = OpenableColumns.SIZE; - values[i++] = -1; + switch (col) { + case OpenableColumns.DISPLAY_NAME: + cols[i] = OpenableColumns.DISPLAY_NAME; + values[i++] = uri.getLastPathSegment(); + break; + case OpenableColumns.SIZE: + cols[i] = OpenableColumns.SIZE; + values[i++] = -1; //Report unknown size; if applications need to know, one need to generate the file here also (count bytes that are written to OutputStream. + break; } } @@ -209,6 +225,7 @@ public class ShareContentProvider extends CustomContentProvider { PipeDataWriter pipeDataWriter = (output, uri1, mimeType, opts, args) -> { try (FileOutputStream fileOutputStream = new FileOutputStream(output.getFileDescriptor())) { + // TODO handle failure (i.e., do not export an empty file) trackExporter.writeTrack(tracks.toArray(new Track[0]), fileOutputStream); } catch (IOException e) { Log.w(TAG, "there occurred an error while sharing a file: " + e); diff --git a/src/main/java/de/dennisguse/opentracks/io/file/TrackFileFormat.java b/src/main/java/de/dennisguse/opentracks/io/file/TrackFileFormat.java index 55ccd5dc6..43c97f044 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/TrackFileFormat.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/TrackFileFormat.java @@ -1,6 +1,7 @@ package de.dennisguse.opentracks.io.file; import android.content.Context; +import android.graphics.Bitmap; import java.util.Locale; @@ -9,6 +10,7 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.io.file.exporter.GPXTrackExporter; import de.dennisguse.opentracks.io.file.exporter.KMLTrackExporter; import de.dennisguse.opentracks.io.file.exporter.KmzTrackExporter; +import de.dennisguse.opentracks.io.file.exporter.SharePictureTrackExporter; import de.dennisguse.opentracks.io.file.exporter.TrackExporter; /** @@ -85,6 +87,7 @@ public enum TrackFileFormat { } }, + GPX { @Override public TrackExporter createTrackExporter(Context context) { @@ -99,6 +102,22 @@ public enum TrackFileFormat { public String getExtension() { return "gpx"; } + }, + + SHARE_PICTURE_PNG { + @Override + public TrackExporter createTrackExporter(Context context) { + return new SharePictureTrackExporter(context, Bitmap.CompressFormat.PNG); + } + + @Override + public String getMimeType() { + return "image/png"; + } + + public String getExtension() { + return "png"; + } }; private static final String MIME_KMZ = "application/vnd.google-earth.kmz"; diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/SharePictureTrackExporter.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/SharePictureTrackExporter.java new file mode 100644 index 000000000..af9246d91 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/SharePictureTrackExporter.java @@ -0,0 +1,88 @@ +package de.dennisguse.opentracks.io.file.exporter; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; + +import androidx.annotation.NonNull; + +import java.io.OutputStream; + +import de.dennisguse.opentracks.content.data.Track; +import de.dennisguse.opentracks.databinding.TrackSharePictureBinding; +import de.dennisguse.opentracks.stats.TrackStatistics; +import de.dennisguse.opentracks.util.PreferencesUtils; +import de.dennisguse.opentracks.util.StringUtils; + +/** + * Create a share picture for a track. + * Here a layout is inflated and converted to PNG. + *

+ * TODO + * If this classes is used with non-visual context (e.g., ApplicationContext), it may not work in the future. + * API31 introduces `ncorrectContextUseViolation`, which may thrown during runtime. + * This code was tested on API30. + * Some more information: https://stackoverflow.com/questions/68539083/inflate-layout-from-applicationcontext-i-e-in-a-contentprovider/68567186#68567186 + *

+ * Future plan: draw directly on a view. + */ +public class SharePictureTrackExporter implements TrackExporter { + + private static final String TAG = SharePictureTrackExporter.class.getSimpleName(); + + private final static int imageWidth = 1024; + + private final Context context; + + private final Bitmap.CompressFormat compressFormat; + + public SharePictureTrackExporter(Context context, Bitmap.CompressFormat compressFormat) { + this.context = context; + this.compressFormat = compressFormat; + } + + @Override + public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) { + if (tracks.length > 1) { + Log.e(TAG, "Not yet implemented."); + return false; + } + + return writeTrack(tracks[0], outputStream); + } + + @Override + public boolean writeTrack(Track track, @NonNull OutputStream outputStream) { + int imageHeight = imageWidth / 4 * 3; + + TrackSharePictureBinding viewBinding = TrackSharePictureBinding.inflate(LayoutInflater.from(context)); + viewBinding.sharePictureCategory.setText(track.getCategory()); + + boolean metricUnits = PreferencesUtils.isMetricUnits(PreferencesUtils.getSharedPreferences(context), context); + TrackStatistics trackStatistics = track.getTrackStatistics(); + + viewBinding.sharePictureTotalTime.setText(StringUtils.formatElapsedTimeWithHour(track.getTrackStatistics().getTotalTime())); + viewBinding.sharePictureTotalDistance.setText(StringUtils.formatDistance(context, track.getTrackStatistics().getTotalDistance(), metricUnits)); + viewBinding.sharePictureGain.setText(StringUtils.formatAltitude(context, trackStatistics.getTotalAltitudeGain(), metricUnits)); + + // prepare rendering + View view = viewBinding.getRoot(); + int widthSpec = View.MeasureSpec.makeMeasureSpec(imageWidth, View.MeasureSpec.EXACTLY); + int heightSpec = View.MeasureSpec.makeMeasureSpec(imageHeight, View.MeasureSpec.EXACTLY); + view.measure(widthSpec, heightSpec); + view.layout(0, 0, imageWidth, imageHeight); + + // draw + Canvas canvas = new Canvas(); + Bitmap bitmap = Bitmap.createBitmap(imageWidth, imageHeight, Bitmap.Config.ARGB_8888); + canvas.setBitmap(bitmap); + + view.draw(canvas); + + // store + return bitmap.compress(compressFormat, 100, outputStream); + } +} diff --git a/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java b/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java index 8911f1929..3cf9f6f8a 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java +++ b/src/main/java/de/dennisguse/opentracks/settings/SettingsActivity.java @@ -330,7 +330,12 @@ public class SettingsActivity extends AbstractActivity implements ChooseActivity private void setExportTrackFileFormatOptions() { - final TrackFileFormat[] trackFileFormats = {TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES, TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA, TrackFileFormat.GPX}; + final TrackFileFormat[] trackFileFormats = { + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES, + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA, + TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA, + TrackFileFormat.GPX + }; String[] entries = new String[trackFileFormats.length]; String[] entryValues = new String[trackFileFormats.length]; diff --git a/src/main/java/de/dennisguse/opentracks/util/IntentUtils.java b/src/main/java/de/dennisguse/opentracks/util/IntentUtils.java index 6775446fb..204a1a5d7 100644 --- a/src/main/java/de/dennisguse/opentracks/util/IntentUtils.java +++ b/src/main/java/de/dennisguse/opentracks/util/IntentUtils.java @@ -29,6 +29,7 @@ import androidx.core.content.FileProvider; 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; @@ -37,6 +38,7 @@ import de.dennisguse.opentracks.content.data.Marker; import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.ShareContentProvider; +import de.dennisguse.opentracks.io.file.TrackFileFormat; /** * Utilities for creating intents. @@ -81,8 +83,6 @@ public class IntentUtils { trackDescription = track == null ? "" : new DescriptionGenerator(context).generateTrackDescription(track, false); } - String mime = ""; - ArrayList uris = new ArrayList<>(); for (Track.Id trackId : trackIds) { Track track = contentProviderUtils.getTrack(trackId); @@ -91,12 +91,14 @@ public class IntentUtils { continue; } - Pair uriAndMime = ShareContentProvider.createURI(trackId, track.getName(), PreferencesUtils.getExportTrackFileFormat(PreferencesUtils.getSharedPreferences(context), context)); - uris.add(uriAndMime.first); - mime = uriAndMime.second; + Pair uriTrackFile = ShareContentProvider.createURI(trackId, track.getName(), PreferencesUtils.getExportTrackFileFormat(PreferencesUtils.getSharedPreferences(context), context)); + Pair uriSharePicture = ShareContentProvider.createURI(trackId, track.getName(), TrackFileFormat.SHARE_PICTURE_PNG); + + uris.addAll(Arrays.asList(uriSharePicture.first, uriTrackFile.first)); } + return new Intent(Intent.ACTION_SEND_MULTIPLE) - .setType(mime) + .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)) diff --git a/src/main/res/layout/track_share_picture.xml b/src/main/res/layout/track_share_picture.xml new file mode 100644 index 000000000..ba1247876 --- /dev/null +++ b/src/main/res/layout/track_share_picture.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file