Loading app/src/main/java/net/fabiszewski/ulogger/DbAccess.java +87 −8 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ import android.database.Cursor; import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; import android.location.Location; import android.net.Uri; import android.util.Log; import androidx.annotation.Nullable; Loading Loading @@ -161,6 +162,30 @@ class DbAccess implements AutoCloseable { DbContract.Positions._ID); } /** * Get result set containing position with given id * * @param id Position id * @return Image URI */ @Nullable private Uri getImageUri(int id) { Cursor query = db.query(DbContract.Positions.TABLE_NAME, new String[]{DbContract.Positions.COLUMN_IMAGE_URI}, DbContract.Positions._ID + "=?", new String[]{Integer.toString(id)}, null, null, null); Uri uri = null; if (query.moveToFirst()) { String imageUri = query.getString(0); if (imageUri != null) { uri = Uri.parse(imageUri); } } query.close(); return uri; } /** * Get result set containing positions marked as not synchronized. * Loading Loading @@ -232,7 +257,7 @@ class DbAccess implements AutoCloseable { * * @param id Position id */ void setSynced(int id) { void setSynced(Context context, int id) { ContentValues values = new ContentValues(); values.put(DbContract.Positions.COLUMN_SYNCED, "1"); values.putNull(DbContract.Positions.COLUMN_ERROR); Loading @@ -240,6 +265,10 @@ class DbAccess implements AutoCloseable { values, DbContract.Positions._ID + "=?", new String[]{String.valueOf(id)}); Uri uri = getImageUri(id); if (uri != null) { ImageHelper.deleteImage(context, uri); } } /** Loading Loading @@ -419,12 +448,12 @@ class DbAccess implements AutoCloseable { } /** * Start new track. * Set up new track. * Deletes all previous track data and positions. Adds new track. * * @param name New track name */ void newTrack(String name) { private void newTrack(String name) { truncateTrack(); truncatePositions(); ContentValues values = new ContentValues(); Loading @@ -433,7 +462,7 @@ class DbAccess implements AutoCloseable { } /** * Start new track. * Set up new track. * Deletes all previous track data and positions. Adds new track. * * @param context Context Loading @@ -441,10 +470,23 @@ class DbAccess implements AutoCloseable { */ static void newTrack(Context context, String name) { try (DbAccess dbAccess = getOpenInstance(context)) { ImageHelper.clearTrackImages(context); dbAccess.newTrack(name); } } /** * Set up new track with default name if there is no active track. * To be used in automated context * @param context Context */ static void newAutoTrack(Context context) { if (getTrackName(context) == null) { newTrack(context, AutoNamePreference.getAutoTrackName(context)); } } /** * Get track summary * Loading @@ -453,10 +495,7 @@ class DbAccess implements AutoCloseable { @Nullable static TrackSummary getTrackSummary(Context context) { try (DbAccess dbAccess = getOpenInstance(context); Cursor positions = db.query(DbContract.Positions.TABLE_NAME, new String[]{"*"}, null, null, null, null, DbContract.Positions._ID)) { Cursor positions = dbAccess.getPositions()) { TrackSummary summary = null; if (positions.moveToFirst()) { double distance = 0.0; Loading Loading @@ -621,6 +660,46 @@ class DbAccess implements AutoCloseable { return !cursor.isNull(cursor.getColumnIndex(DbContract.Positions.COLUMN_PROVIDER)); } /** * Get comment from positions cursor * * @param cursor Cursor * @return String comment */ static String getComment(Cursor cursor) { return cursor.getString(cursor.getColumnIndex(DbContract.Positions.COLUMN_COMMENT)); } /** * Check if cursor contains image URI * * @param cursor Cursor * @return True if has image URI */ static boolean hasImageUri(Cursor cursor) { return !cursor.isNull(cursor.getColumnIndex(DbContract.Positions.COLUMN_IMAGE_URI)); } /** * Get image URI from positions cursor * * @param cursor Cursor * @return String URI */ static String getImageUri(Cursor cursor) { return cursor.getString(cursor.getColumnIndex(DbContract.Positions.COLUMN_IMAGE_URI)); } /** * Check if cursor contains comment data * * @param cursor Cursor * @return True if has comment data */ static boolean hasComment(Cursor cursor) { return !cursor.isNull(cursor.getColumnIndex(DbContract.Positions.COLUMN_COMMENT)); } /** * Get latitude from positions cursor * Loading app/src/main/java/net/fabiszewski/ulogger/ExternalCommandReceiver.java +1 −3 Original line number Diff line number Diff line Loading @@ -54,9 +54,7 @@ public class ExternalCommandReceiver extends BroadcastReceiver { * @param context Context */ private void startLoggerService(Context context) { if (DbAccess.getTrackName(context) == null) { DbAccess.newTrack(context, AutoNamePreference.getAutoTrackName(context)); } DbAccess.newAutoTrack(context); Intent intent = new Intent(context, LoggerService.class); ContextCompat.startForegroundService(context, intent); } Loading app/src/main/java/net/fabiszewski/ulogger/GpxExportService.java +3 −0 Original line number Diff line number Diff line Loading @@ -171,6 +171,9 @@ public class GpxExportService extends IntentService { } writeTag(serializer, "time", DbAccess.getTimeISO8601(cursor)); writeTag(serializer, "name", DbAccess.getID(cursor)); if (DbAccess.hasComment(cursor)) { writeTag(serializer, "desc", DbAccess.getComment(cursor)); } // ulogger extensions (accuracy, speed, bearing, provider) serializer.startTag(null, "extensions"); Loading app/src/main/java/net/fabiszewski/ulogger/ImageHelper.java +171 −51 Original line number Diff line number Diff line Loading @@ -9,23 +9,30 @@ package net.fabiszewski.ulogger; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.ImageDecoder; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.media.ThumbnailUtils; import android.net.Uri; import android.os.Build; import android.provider.MediaStore; import android.provider.OpenableColumns; import android.util.Log; import android.webkit.MimeTypeMap; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import androidx.annotation.Nullable; import androidx.exifinterface.media.ExifInterface; import androidx.preference.PreferenceManager; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.text.SimpleDateFormat; Loading @@ -36,11 +43,16 @@ class ImageHelper { private static final String TAG = ImageHelper.class.getSimpleName(); private static final String MEDIA_ORIENTATION = "orientation"; private static final String JPEG_MIME = "image/jpg"; private static final int ORIENTATION_90 = 90; private static final int ORIENTATION_180 = 180; private static final int ORIENTATION_270 = 270; private static final int ORIENTATION_NORMAL = 0; /** * Get orientation * First try MediaStore, then Exif * * @param context Context * @param uri Image URI * @return Orientation in degrees Loading @@ -55,31 +67,34 @@ class ImageHelper { /** * Get orientation data from EXIF * * @param context Context * @param uri Image URI * @return Orientation in degrees */ private static int getOrientationExif(Context context, Uri uri) { int orientation = 0; int orientation = ORIENTATION_NORMAL; try (InputStream in = context.getContentResolver().openInputStream(uri)) { if (in != null) { ExifInterface exif = new ExifInterface(in); int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1); if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_90) { orientation = 90; orientation = ORIENTATION_90; } else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_180) { orientation = 180; orientation = ORIENTATION_180; } else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_270) { orientation = 270; orientation = ORIENTATION_270; } } } catch (Exception ignored) {} } catch (Exception ignored) { } if (Logger.DEBUG) { Log.d(TAG, "[getOrientationExif: " + orientation + "]"); } return orientation; } /** * Get orientation data from MediaStore * * @param context Context * @param uri Image URI * @return Orientation in degrees Loading @@ -91,54 +106,32 @@ class ImageHelper { if (cursor != null && cursor.moveToFirst()) { orientation = cursor.getInt(0); } } catch (Exception ignored) {} } catch (Exception ignored) { } if (Logger.DEBUG) { Log.d(TAG, "[getOrientationMediaStore: " + orientation + "]"); } return orientation; } /** * Get bitmap from URI * @param context Context * @param uri URI * @return Bitmap * @throws IOException Exception * Creates pseudounique name based on timestamp * * @return Name */ @RequiresApi(api = Build.VERSION_CODES.P) @NonNull private static Bitmap getBitmap(Context context, Uri uri) throws IOException { Bitmap bitmap; ImageDecoder.Source src = ImageDecoder.createSource(context.getContentResolver(), uri); bitmap = ImageDecoder.decodeBitmap(src); return bitmap; } /** * Get bitmap from URI * @param context Context * @param uri URI * @return Bitmap * @throws IOException Exception */ @SuppressWarnings({"deprecation", "RedundantSuppression"}) private static Bitmap getBitmapLegacy(Context context, Uri uri) throws IOException { Bitmap bitmap; bitmap = MediaStore.Images.Media.getBitmap(context.getContentResolver(), uri); return bitmap; } @NonNull private static String getFileName() { private static String getUniqueName() { String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date()); return "ulogger_" + timeStamp + "_"; return "ulogger_" + timeStamp; } /** * Create image uri in media collection * * @param context Context * @return URI */ static Uri createImageUri(Context context) { ContentValues values = new ContentValues(); values.put(MediaStore.Images.Media.TITLE, getFileName()); values.put(MediaStore.Images.Media.TITLE, getUniqueName()); values.put(MediaStore.Images.Media.MIME_TYPE, JPEG_MIME); Uri collection; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { Loading @@ -149,20 +142,20 @@ class ImageHelper { return context.getContentResolver().insert(collection, values); } static Bitmap getThumbnail(Context context, Uri uri) throws IOException { int sizeDp = (int) context.getResources().getDimension(R.dimen.thumbnail_size); int sizePx = sizeDp * (int) Resources.getSystem().getDisplayMetrics().density; Bitmap bitmap; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { bitmap = ImageHelper.getBitmapLegacy(context, uri); } else { bitmap = ImageHelper.getBitmap(context, uri); ContentResolver cr = context.getContentResolver(); try (InputStream is = cr.openInputStream(uri)) { bitmap = BitmapFactory.decodeStream(is, null, null); } Bitmap thumbBitmap = ThumbnailUtils.extractThumbnail(bitmap, sizePx, sizePx); bitmap.recycle(); thumbBitmap = fixImageOrientation(context, uri, thumbBitmap); return thumbBitmap; bitmap = ThumbnailUtils.extractThumbnail(bitmap, sizePx, sizePx); bitmap = fixImageOrientation(context, uri, bitmap); return bitmap; } private static Bitmap fixImageOrientation(Context context, Uri uri, Bitmap bitmap) { Loading @@ -174,8 +167,135 @@ class ImageHelper { bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); } } catch (Exception e) { if (Logger.DEBUG) { Log.d(TAG, "[setThumbnail exception: " + e + "]"); } if (Logger.DEBUG) { Log.d(TAG, "[fixImageOrientation exception: " + e + "]" ); } } return bitmap; } static long getFileSize(Context context, Uri uri) { long fileSize = 0; try (Cursor cursor = context.getContentResolver() .query(uri, null, null, null, null)) { if (cursor != null) { int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE); cursor.moveToFirst(); fileSize = cursor.getLong(sizeIndex); } } return fileSize; } @Nullable static String getFileMime(Context context, Uri uri) { ContentResolver cr = context.getContentResolver(); String fileMime = cr.getType(uri); if (fileMime == null) { String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString()); fileMime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension.toLowerCase()); } return fileMime; } static Uri resampleIfNeeded(Context context, Uri uri) throws IOException { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); int dstWidth = Integer.parseInt(prefs.getString(SettingsActivity.KEY_IMAGE_SIZE, context.getString(R.string.pref_imagesize_default))); if (dstWidth == 0) { return uri; } ContentResolver cr = context.getContentResolver(); BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; try (InputStream is = cr.openInputStream(uri)) { BitmapFactory.decodeStream(is, null, options); } int srcWidth = Math.max(options.outWidth, options.outHeight); int scale = srcWidth / dstWidth; if (Logger.DEBUG) { Log.d(TAG, "[resampleIfNeeded scale: " + scale + "]"); } options = new BitmapFactory.Options(); Bitmap bitmap = null; boolean retry = false; do { try { if (scale > 1) { options.inScaled = true; options.inSampleSize = 1; options.inDensity = srcWidth; options.inTargetDensity = dstWidth * options.inSampleSize; } try (InputStream is = cr.openInputStream(uri)) { bitmap = BitmapFactory.decodeStream(is, null, options); } } catch (OutOfMemoryError e) { if (Logger.DEBUG) { Log.d(TAG, "[resampleIfNeeded OutOfMemoryError]"); } } if (bitmap == null && scale > 1) { options.inSampleSize = scale; retry = !retry; if (Logger.DEBUG) { Log.d(TAG, "[resampleIfNeeded try sampling: " + retry + "]"); } } } while (retry); if (bitmap == null) { throw new IOException("Failed to decode image"); } bitmap = fixImageOrientation(context, uri, bitmap); String filename = getUniqueName() + ".jpg"; File outFile = new File(context.getCacheDir(), filename); try (FileOutputStream os = new FileOutputStream(outFile)) { bitmap.compress(Bitmap.CompressFormat.JPEG, 90, os); } return Uri.fromFile(outFile); } static Uri moveToAppStorage(@NonNull Context context, @NonNull Uri inUri) { Uri outUri = null; String path = inUri.getPath(); if (path != null) { if (!path.startsWith(context.getCacheDir().getPath())) { return inUri; } File inFile = new File(path); File outFile = new File(context.getFilesDir(), inFile.getName()); if (inFile.renameTo(outFile)) { outUri = Uri.fromFile(outFile); } else { if (Logger.DEBUG) { Log.d(TAG, "[moveToAppStorage failed]"); } } } return outUri; } static void clearImageCache(@NonNull Context context) { File dir = context.getCacheDir(); clearImages(dir); } static void clearTrackImages(@NonNull Context context) { File dir = context.getFilesDir(); clearImages(dir); } private static void clearImages(File dir) { File[] files = dir.listFiles(); if (files != null) { for (File file : files) { if (file.isFile() && file.getPath().endsWith(".jpg") && file.delete()) { if (Logger.DEBUG) { Log.d(TAG, "[clearImages deleted file " + file.getName() + "]"); } } } } } static void deleteImage(Context context, Uri uri) { String path = uri.getPath(); if (path != null) { File file = new File(path); if (file.isFile() && file.getPath().startsWith(context.getFilesDir().getPath()) && file.delete()) { if (Logger.DEBUG) { Log.d(TAG, "[deleteImage deleted file " + file.getName() + "]"); } } } } } app/src/main/java/net/fabiszewski/ulogger/RestartBroadcastReceiver.java +1 −6 Original line number Diff line number Diff line Loading @@ -62,12 +62,7 @@ public class RestartBroadcastReceiver extends BroadcastReceiver { * @param context Context */ private void startLoggerService(Context context) { DbAccess db = DbAccess.getInstance(); db.open(context); if (db.getTrackName() == null) { db.newTrack(AutoNamePreference.getAutoTrackName(context)); } db.close(); DbAccess.newAutoTrack(context); Intent intent = new Intent(context, LoggerService.class); ContextCompat.startForegroundService(context, intent); } Loading Loading
app/src/main/java/net/fabiszewski/ulogger/DbAccess.java +87 −8 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ import android.database.Cursor; import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; import android.location.Location; import android.net.Uri; import android.util.Log; import androidx.annotation.Nullable; Loading Loading @@ -161,6 +162,30 @@ class DbAccess implements AutoCloseable { DbContract.Positions._ID); } /** * Get result set containing position with given id * * @param id Position id * @return Image URI */ @Nullable private Uri getImageUri(int id) { Cursor query = db.query(DbContract.Positions.TABLE_NAME, new String[]{DbContract.Positions.COLUMN_IMAGE_URI}, DbContract.Positions._ID + "=?", new String[]{Integer.toString(id)}, null, null, null); Uri uri = null; if (query.moveToFirst()) { String imageUri = query.getString(0); if (imageUri != null) { uri = Uri.parse(imageUri); } } query.close(); return uri; } /** * Get result set containing positions marked as not synchronized. * Loading Loading @@ -232,7 +257,7 @@ class DbAccess implements AutoCloseable { * * @param id Position id */ void setSynced(int id) { void setSynced(Context context, int id) { ContentValues values = new ContentValues(); values.put(DbContract.Positions.COLUMN_SYNCED, "1"); values.putNull(DbContract.Positions.COLUMN_ERROR); Loading @@ -240,6 +265,10 @@ class DbAccess implements AutoCloseable { values, DbContract.Positions._ID + "=?", new String[]{String.valueOf(id)}); Uri uri = getImageUri(id); if (uri != null) { ImageHelper.deleteImage(context, uri); } } /** Loading Loading @@ -419,12 +448,12 @@ class DbAccess implements AutoCloseable { } /** * Start new track. * Set up new track. * Deletes all previous track data and positions. Adds new track. * * @param name New track name */ void newTrack(String name) { private void newTrack(String name) { truncateTrack(); truncatePositions(); ContentValues values = new ContentValues(); Loading @@ -433,7 +462,7 @@ class DbAccess implements AutoCloseable { } /** * Start new track. * Set up new track. * Deletes all previous track data and positions. Adds new track. * * @param context Context Loading @@ -441,10 +470,23 @@ class DbAccess implements AutoCloseable { */ static void newTrack(Context context, String name) { try (DbAccess dbAccess = getOpenInstance(context)) { ImageHelper.clearTrackImages(context); dbAccess.newTrack(name); } } /** * Set up new track with default name if there is no active track. * To be used in automated context * @param context Context */ static void newAutoTrack(Context context) { if (getTrackName(context) == null) { newTrack(context, AutoNamePreference.getAutoTrackName(context)); } } /** * Get track summary * Loading @@ -453,10 +495,7 @@ class DbAccess implements AutoCloseable { @Nullable static TrackSummary getTrackSummary(Context context) { try (DbAccess dbAccess = getOpenInstance(context); Cursor positions = db.query(DbContract.Positions.TABLE_NAME, new String[]{"*"}, null, null, null, null, DbContract.Positions._ID)) { Cursor positions = dbAccess.getPositions()) { TrackSummary summary = null; if (positions.moveToFirst()) { double distance = 0.0; Loading Loading @@ -621,6 +660,46 @@ class DbAccess implements AutoCloseable { return !cursor.isNull(cursor.getColumnIndex(DbContract.Positions.COLUMN_PROVIDER)); } /** * Get comment from positions cursor * * @param cursor Cursor * @return String comment */ static String getComment(Cursor cursor) { return cursor.getString(cursor.getColumnIndex(DbContract.Positions.COLUMN_COMMENT)); } /** * Check if cursor contains image URI * * @param cursor Cursor * @return True if has image URI */ static boolean hasImageUri(Cursor cursor) { return !cursor.isNull(cursor.getColumnIndex(DbContract.Positions.COLUMN_IMAGE_URI)); } /** * Get image URI from positions cursor * * @param cursor Cursor * @return String URI */ static String getImageUri(Cursor cursor) { return cursor.getString(cursor.getColumnIndex(DbContract.Positions.COLUMN_IMAGE_URI)); } /** * Check if cursor contains comment data * * @param cursor Cursor * @return True if has comment data */ static boolean hasComment(Cursor cursor) { return !cursor.isNull(cursor.getColumnIndex(DbContract.Positions.COLUMN_COMMENT)); } /** * Get latitude from positions cursor * Loading
app/src/main/java/net/fabiszewski/ulogger/ExternalCommandReceiver.java +1 −3 Original line number Diff line number Diff line Loading @@ -54,9 +54,7 @@ public class ExternalCommandReceiver extends BroadcastReceiver { * @param context Context */ private void startLoggerService(Context context) { if (DbAccess.getTrackName(context) == null) { DbAccess.newTrack(context, AutoNamePreference.getAutoTrackName(context)); } DbAccess.newAutoTrack(context); Intent intent = new Intent(context, LoggerService.class); ContextCompat.startForegroundService(context, intent); } Loading
app/src/main/java/net/fabiszewski/ulogger/GpxExportService.java +3 −0 Original line number Diff line number Diff line Loading @@ -171,6 +171,9 @@ public class GpxExportService extends IntentService { } writeTag(serializer, "time", DbAccess.getTimeISO8601(cursor)); writeTag(serializer, "name", DbAccess.getID(cursor)); if (DbAccess.hasComment(cursor)) { writeTag(serializer, "desc", DbAccess.getComment(cursor)); } // ulogger extensions (accuracy, speed, bearing, provider) serializer.startTag(null, "extensions"); Loading
app/src/main/java/net/fabiszewski/ulogger/ImageHelper.java +171 −51 Original line number Diff line number Diff line Loading @@ -9,23 +9,30 @@ package net.fabiszewski.ulogger; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.ImageDecoder; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.media.ThumbnailUtils; import android.net.Uri; import android.os.Build; import android.provider.MediaStore; import android.provider.OpenableColumns; import android.util.Log; import android.webkit.MimeTypeMap; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import androidx.annotation.Nullable; import androidx.exifinterface.media.ExifInterface; import androidx.preference.PreferenceManager; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.text.SimpleDateFormat; Loading @@ -36,11 +43,16 @@ class ImageHelper { private static final String TAG = ImageHelper.class.getSimpleName(); private static final String MEDIA_ORIENTATION = "orientation"; private static final String JPEG_MIME = "image/jpg"; private static final int ORIENTATION_90 = 90; private static final int ORIENTATION_180 = 180; private static final int ORIENTATION_270 = 270; private static final int ORIENTATION_NORMAL = 0; /** * Get orientation * First try MediaStore, then Exif * * @param context Context * @param uri Image URI * @return Orientation in degrees Loading @@ -55,31 +67,34 @@ class ImageHelper { /** * Get orientation data from EXIF * * @param context Context * @param uri Image URI * @return Orientation in degrees */ private static int getOrientationExif(Context context, Uri uri) { int orientation = 0; int orientation = ORIENTATION_NORMAL; try (InputStream in = context.getContentResolver().openInputStream(uri)) { if (in != null) { ExifInterface exif = new ExifInterface(in); int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1); if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_90) { orientation = 90; orientation = ORIENTATION_90; } else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_180) { orientation = 180; orientation = ORIENTATION_180; } else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_270) { orientation = 270; orientation = ORIENTATION_270; } } } catch (Exception ignored) {} } catch (Exception ignored) { } if (Logger.DEBUG) { Log.d(TAG, "[getOrientationExif: " + orientation + "]"); } return orientation; } /** * Get orientation data from MediaStore * * @param context Context * @param uri Image URI * @return Orientation in degrees Loading @@ -91,54 +106,32 @@ class ImageHelper { if (cursor != null && cursor.moveToFirst()) { orientation = cursor.getInt(0); } } catch (Exception ignored) {} } catch (Exception ignored) { } if (Logger.DEBUG) { Log.d(TAG, "[getOrientationMediaStore: " + orientation + "]"); } return orientation; } /** * Get bitmap from URI * @param context Context * @param uri URI * @return Bitmap * @throws IOException Exception * Creates pseudounique name based on timestamp * * @return Name */ @RequiresApi(api = Build.VERSION_CODES.P) @NonNull private static Bitmap getBitmap(Context context, Uri uri) throws IOException { Bitmap bitmap; ImageDecoder.Source src = ImageDecoder.createSource(context.getContentResolver(), uri); bitmap = ImageDecoder.decodeBitmap(src); return bitmap; } /** * Get bitmap from URI * @param context Context * @param uri URI * @return Bitmap * @throws IOException Exception */ @SuppressWarnings({"deprecation", "RedundantSuppression"}) private static Bitmap getBitmapLegacy(Context context, Uri uri) throws IOException { Bitmap bitmap; bitmap = MediaStore.Images.Media.getBitmap(context.getContentResolver(), uri); return bitmap; } @NonNull private static String getFileName() { private static String getUniqueName() { String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date()); return "ulogger_" + timeStamp + "_"; return "ulogger_" + timeStamp; } /** * Create image uri in media collection * * @param context Context * @return URI */ static Uri createImageUri(Context context) { ContentValues values = new ContentValues(); values.put(MediaStore.Images.Media.TITLE, getFileName()); values.put(MediaStore.Images.Media.TITLE, getUniqueName()); values.put(MediaStore.Images.Media.MIME_TYPE, JPEG_MIME); Uri collection; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { Loading @@ -149,20 +142,20 @@ class ImageHelper { return context.getContentResolver().insert(collection, values); } static Bitmap getThumbnail(Context context, Uri uri) throws IOException { int sizeDp = (int) context.getResources().getDimension(R.dimen.thumbnail_size); int sizePx = sizeDp * (int) Resources.getSystem().getDisplayMetrics().density; Bitmap bitmap; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { bitmap = ImageHelper.getBitmapLegacy(context, uri); } else { bitmap = ImageHelper.getBitmap(context, uri); ContentResolver cr = context.getContentResolver(); try (InputStream is = cr.openInputStream(uri)) { bitmap = BitmapFactory.decodeStream(is, null, null); } Bitmap thumbBitmap = ThumbnailUtils.extractThumbnail(bitmap, sizePx, sizePx); bitmap.recycle(); thumbBitmap = fixImageOrientation(context, uri, thumbBitmap); return thumbBitmap; bitmap = ThumbnailUtils.extractThumbnail(bitmap, sizePx, sizePx); bitmap = fixImageOrientation(context, uri, bitmap); return bitmap; } private static Bitmap fixImageOrientation(Context context, Uri uri, Bitmap bitmap) { Loading @@ -174,8 +167,135 @@ class ImageHelper { bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); } } catch (Exception e) { if (Logger.DEBUG) { Log.d(TAG, "[setThumbnail exception: " + e + "]"); } if (Logger.DEBUG) { Log.d(TAG, "[fixImageOrientation exception: " + e + "]" ); } } return bitmap; } static long getFileSize(Context context, Uri uri) { long fileSize = 0; try (Cursor cursor = context.getContentResolver() .query(uri, null, null, null, null)) { if (cursor != null) { int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE); cursor.moveToFirst(); fileSize = cursor.getLong(sizeIndex); } } return fileSize; } @Nullable static String getFileMime(Context context, Uri uri) { ContentResolver cr = context.getContentResolver(); String fileMime = cr.getType(uri); if (fileMime == null) { String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString()); fileMime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension.toLowerCase()); } return fileMime; } static Uri resampleIfNeeded(Context context, Uri uri) throws IOException { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); int dstWidth = Integer.parseInt(prefs.getString(SettingsActivity.KEY_IMAGE_SIZE, context.getString(R.string.pref_imagesize_default))); if (dstWidth == 0) { return uri; } ContentResolver cr = context.getContentResolver(); BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; try (InputStream is = cr.openInputStream(uri)) { BitmapFactory.decodeStream(is, null, options); } int srcWidth = Math.max(options.outWidth, options.outHeight); int scale = srcWidth / dstWidth; if (Logger.DEBUG) { Log.d(TAG, "[resampleIfNeeded scale: " + scale + "]"); } options = new BitmapFactory.Options(); Bitmap bitmap = null; boolean retry = false; do { try { if (scale > 1) { options.inScaled = true; options.inSampleSize = 1; options.inDensity = srcWidth; options.inTargetDensity = dstWidth * options.inSampleSize; } try (InputStream is = cr.openInputStream(uri)) { bitmap = BitmapFactory.decodeStream(is, null, options); } } catch (OutOfMemoryError e) { if (Logger.DEBUG) { Log.d(TAG, "[resampleIfNeeded OutOfMemoryError]"); } } if (bitmap == null && scale > 1) { options.inSampleSize = scale; retry = !retry; if (Logger.DEBUG) { Log.d(TAG, "[resampleIfNeeded try sampling: " + retry + "]"); } } } while (retry); if (bitmap == null) { throw new IOException("Failed to decode image"); } bitmap = fixImageOrientation(context, uri, bitmap); String filename = getUniqueName() + ".jpg"; File outFile = new File(context.getCacheDir(), filename); try (FileOutputStream os = new FileOutputStream(outFile)) { bitmap.compress(Bitmap.CompressFormat.JPEG, 90, os); } return Uri.fromFile(outFile); } static Uri moveToAppStorage(@NonNull Context context, @NonNull Uri inUri) { Uri outUri = null; String path = inUri.getPath(); if (path != null) { if (!path.startsWith(context.getCacheDir().getPath())) { return inUri; } File inFile = new File(path); File outFile = new File(context.getFilesDir(), inFile.getName()); if (inFile.renameTo(outFile)) { outUri = Uri.fromFile(outFile); } else { if (Logger.DEBUG) { Log.d(TAG, "[moveToAppStorage failed]"); } } } return outUri; } static void clearImageCache(@NonNull Context context) { File dir = context.getCacheDir(); clearImages(dir); } static void clearTrackImages(@NonNull Context context) { File dir = context.getFilesDir(); clearImages(dir); } private static void clearImages(File dir) { File[] files = dir.listFiles(); if (files != null) { for (File file : files) { if (file.isFile() && file.getPath().endsWith(".jpg") && file.delete()) { if (Logger.DEBUG) { Log.d(TAG, "[clearImages deleted file " + file.getName() + "]"); } } } } } static void deleteImage(Context context, Uri uri) { String path = uri.getPath(); if (path != null) { File file = new File(path); if (file.isFile() && file.getPath().startsWith(context.getFilesDir().getPath()) && file.delete()) { if (Logger.DEBUG) { Log.d(TAG, "[deleteImage deleted file " + file.getName() + "]"); } } } } }
app/src/main/java/net/fabiszewski/ulogger/RestartBroadcastReceiver.java +1 −6 Original line number Diff line number Diff line Loading @@ -62,12 +62,7 @@ public class RestartBroadcastReceiver extends BroadcastReceiver { * @param context Context */ private void startLoggerService(Context context) { DbAccess db = DbAccess.getInstance(); db.open(context); if (db.getTrackName() == null) { db.newTrack(AutoNamePreference.getAutoTrackName(context)); } db.close(); DbAccess.newAutoTrack(context); Intent intent = new Intent(context, LoggerService.class); ContextCompat.startForegroundService(context, intent); } Loading