Loading app/src/main/AndroidManifest.xml +3 −1 Original line number Diff line number Diff line Loading @@ -13,7 +13,9 @@ <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- For writing camera images API <= 28 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" tools:ignore="ScopedStorage" /> <!-- For auto start --> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <!-- For web sync retries --> Loading app/src/main/java/net/fabiszewski/ulogger/ImageTask.java +57 −16 Original line number Diff line number Diff line Loading @@ -13,16 +13,20 @@ import android.app.Activity; import android.content.SharedPreferences; import android.graphics.Bitmap; import android.net.Uri; import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.annotation.WorkerThread; import androidx.preference.PreferenceManager; import java.io.IOException; import java.lang.ref.WeakReference; import static net.fabiszewski.ulogger.ImageHelper.clearImageCache; import static net.fabiszewski.ulogger.ImageHelper.getPersistablePermission; import static net.fabiszewski.ulogger.ImageHelper.getResampledBitmap; import static net.fabiszewski.ulogger.ImageHelper.getThumbnail; Loading @@ -32,26 +36,53 @@ import static net.fabiszewski.ulogger.ImageHelper.saveToCache; /** * Task to downsample image */ class ImageTask extends AsyncTask<Uri, Void, ImageTask.ImageTaskResult> { class ImageTask implements Runnable { private static final String TAG = ImageTask.class.getSimpleName(); private final WeakReference<ImageTaskCallback> weakCallback; private String errorMessage = "Image resampling failed"; private String errorMessage = ""; private final Uri uri; ImageTask(ImageTaskCallback callback) { private boolean isRunning = false; private boolean isCancelled = false; private final Handler uiHandler = new Handler(Looper.getMainLooper()); ImageTask(Uri uri, ImageTaskCallback callback) { this.uri = uri; weakCallback = new WeakReference<>(callback); } @Override protected ImageTaskResult doInBackground(Uri... params) { public void run() { isRunning = true; ImageTaskResult result = doInBackground(); if (isCancelled) { cleanUp(result); } else { uiHandler.post(() -> onPostExecute(result)); } isRunning = false; } public void cancel() { if (Logger.DEBUG) { Log.d(TAG, "[task cancelled]"); } isCancelled = true; } public boolean isRunning() { return isRunning; } @WorkerThread private ImageTaskResult doInBackground() { if (Logger.DEBUG) { Log.d(TAG, "[doInBackground]"); } Activity activity = getActivity(); if (activity == null || params.length != 1 || params[0] == null) { if (activity == null) { return null; } Uri inUri = params[0]; ImageTaskResult result = null; try { Uri savedUri; Loading @@ -60,11 +91,11 @@ class ImageTask extends AsyncTask<Uri, Void, ImageTask.ImageTaskResult> { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity); int dstWidth = Integer.parseInt(prefs.getString(SettingsActivity.KEY_IMAGE_SIZE, activity.getString(R.string.pref_imagesize_default))); if (dstWidth == 0) { savedUri = inUri; getPersistablePermission(activity, inUri); thumbnail = getThumbnail(activity, inUri); savedUri = uri; getPersistablePermission(activity, uri); thumbnail = getThumbnail(activity, uri); } else { Bitmap bitmap = getResampledBitmap(activity, inUri, dstWidth); Bitmap bitmap = getResampledBitmap(activity, uri, dstWidth); savedUri = saveToCache(activity, bitmap); thumbnail = getThumbnail(activity, bitmap); bitmap.recycle(); Loading @@ -74,17 +105,16 @@ class ImageTask extends AsyncTask<Uri, Void, ImageTask.ImageTaskResult> { } } catch (IOException e) { if (e.getMessage() != null) { errorMessage += ": " + e.getMessage(); errorMessage = e.getMessage(); } } return result; } @Override protected void onPostExecute(@Nullable ImageTaskResult result) { super.onPostExecute(result); @UiThread private void onPostExecute(@Nullable ImageTaskResult result) { ImageTaskCallback callback = weakCallback.get(); if (callback != null) { if (callback != null && callback.getActivity() != null) { if (result == null) { callback.onImageTaskFailure(errorMessage); } else { Loading @@ -102,6 +132,17 @@ class ImageTask extends AsyncTask<Uri, Void, ImageTask.ImageTaskResult> { return null; } /** * Try to clean image cache * @param result Task result */ private void cleanUp(ImageTaskResult result) { Activity activity = getActivity(); if (result != null && activity != null) { clearImageCache(activity.getApplicationContext()); } } interface ImageTaskCallback { void onImageTaskCompleted(@NonNull Uri uri, @NonNull Bitmap thumbnail); void onImageTaskFailure(@NonNull String error); Loading app/src/main/java/net/fabiszewski/ulogger/LocationHelper.java +1 −1 Original line number Diff line number Diff line Loading @@ -167,6 +167,7 @@ class LocationHelper { * @param singleShot Request single update if true * @throws LoggerException Exception on permission denied or provider disabled */ @SuppressWarnings({"deprecation", "RedundantSuppression"}) private void requestProviderUpdates(@NonNull String provider, @NonNull LocationListener listener, @Nullable Looper looper, boolean singleShot) throws LoggerException { if (Logger.DEBUG) { Log.d(TAG, "[requestProviderUpdates: " + provider + " (" + singleShot + ")]"); } try { Loading @@ -174,7 +175,6 @@ class LocationHelper { // request even if provider is disabled to allow users re-enable it later locationManager.requestLocationUpdates(provider, minTimeMillis, minDistance, listener, looper); } else if (locationManager.isProviderEnabled(provider)) { // FIXME: original caller should be rewritten instead? if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { locationManager.getCurrentLocation(provider, null, runnable -> { if (looper != null) { Loading app/src/main/java/net/fabiszewski/ulogger/LoggerTask.java +70 −38 Original line number Diff line number Diff line Loading @@ -12,13 +12,15 @@ package net.fabiszewski.ulogger; import android.app.Activity; import android.location.Location; import android.location.LocationListener; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.annotation.WorkerThread; import java.lang.ref.WeakReference; Loading @@ -26,7 +28,7 @@ import java.lang.ref.WeakReference; /** * Task to get location according to user preferences criteria */ class LoggerTask extends AsyncTask<Void, Void, Location> implements LocationListener { class LoggerTask implements LocationListener, Runnable { private static final String TAG = LoggerTask.class.getSimpleName(); private static final int E_OK = 0; Loading @@ -38,6 +40,11 @@ class LoggerTask extends AsyncTask<Void, Void, Location> implements LocationList private final LocationHelper locationHelper; private Location location; private volatile boolean waiting; private boolean isCancelled = false; private boolean isRunning = false; private final Handler uiHandler = new Handler(Looper.getMainLooper()); private final Object lock = new Object(); private int error = LocationHelper.LoggerException.E_OK; Loading @@ -47,7 +54,31 @@ class LoggerTask extends AsyncTask<Void, Void, Location> implements LocationList } @Override protected Location doInBackground(Void... voids) { public void run() { isRunning = true; Location location = doInBackground(); if (!isCancelled) { uiHandler.post(() -> onPostExecute(location)); } isRunning = false; } public void cancel() { if (Logger.DEBUG) { Log.d(TAG, "[task cancelled]"); } isCancelled = true; quitLoop(); } public boolean isRunning() { return isRunning; } /** * Requests location update, waits in loop * @return Location or null if none */ @WorkerThread private Location doInBackground() { if (Logger.DEBUG) { Log.d(TAG, "[doInBackground]"); } Activity activity = getActivity(); if (activity == null) { Loading @@ -57,52 +88,49 @@ class LoggerTask extends AsyncTask<Void, Void, Location> implements LocationList error = E_PERMISSION; return null; } synchronized (lock) { waiting = true; final long startTime = System.currentTimeMillis(); try { locationHelper.requestSingleUpdate(this, Looper.getMainLooper()); loop(); while (waiting) { try { lock.wait(TIMEOUT_MS); } catch (InterruptedException e) { if (Logger.DEBUG) { Log.d(TAG, "[loop interrupted]"); } } if (System.currentTimeMillis() - startTime >= TIMEOUT_MS) { if (Logger.DEBUG) { Log.d(TAG, "[loop timeout]"); } waiting = false; } } locationHelper.removeUpdates(this); return location; } catch (LocationHelper.LoggerException e) { error = e.getCode(); } } return null; } /** * Loop on worker thread * Quit loop */ private void loop() { waiting = true; final long startTime = System.currentTimeMillis(); while (waiting) { if (System.currentTimeMillis() - startTime > TIMEOUT_MS) { if (Logger.DEBUG) { Log.d(TAG, "[loop timeout]"); } break; } if (isCancelled()) { if (Logger.DEBUG) { Log.d(TAG, "[loop cancelled]"); } break; } try { Thread.sleep(500); } catch (InterruptedException ignored) { break; } private synchronized void quitLoop() { synchronized (lock) { waiting = false; lock.notifyAll(); } } /** * Quit loop * Execute callback with location result * @param location Location */ private void quitLoop() { waiting = false; } @Override protected void onPostExecute(Location location) { super.onPostExecute(location); @UiThread private void onPostExecute(Location location) { LoggerTaskCallback callback = weakCallback.get(); if (callback != null) { if (callback != null && callback.getActivity() != null) { if (error == E_OK && location != null) { callback.onLoggerTaskCompleted(location); } else { Loading Loading @@ -133,6 +161,7 @@ class LoggerTask extends AsyncTask<Void, Void, Location> implements LocationList } @UiThread @Override public void onLocationChanged(@NonNull Location location) { if (Logger.DEBUG) { Log.d(TAG, "[location changed: " + location + "]"); } Loading Loading @@ -161,10 +190,12 @@ class LoggerTask extends AsyncTask<Void, Void, Location> implements LocationList return true; } @SuppressWarnings({"deprecation", "RedundantSuppression"}) @UiThread @Override @SuppressWarnings({"deprecation", "RedundantSuppression"}) public void onStatusChanged(String provider, int status, Bundle extras) { } @UiThread @Override public void onProviderEnabled(@NonNull String provider) { if (Logger.DEBUG) { Log.d(TAG, "[onProviderEnabled: " + provider + "]"); } Loading @@ -175,6 +206,7 @@ class LoggerTask extends AsyncTask<Void, Void, Location> implements LocationList } } @UiThread @Override public void onProviderDisabled(@NonNull String provider) { if (Logger.DEBUG) { Log.d(TAG, "[onProviderDisabled: " + provider + "]"); } Loading app/src/main/java/net/fabiszewski/ulogger/MainActivity.java +18 −21 Original line number Diff line number Diff line Loading @@ -130,28 +130,25 @@ public class MainActivity extends AppCompatActivity */ @Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { switch (item.getItemId()) { case R.id.menu_settings: final int id = item.getItemId(); if (id == R.id.menu_settings) { Intent i = new Intent(this, SettingsActivity.class); startActivityForResult(i, RESULT_PREFS_UPDATED); return true; case R.id.menu_about: } else if (id == R.id.menu_about) { showAbout(); return true; case R.id.menu_export: } else if (id == R.id.menu_export) { startExport(); return true; case R.id.menu_clear: } else if (id == R.id.menu_clear) { clearTrack(); return true; case android.R.id.home: } else if (id == android.R.id.home) { onBackPressed(); return true; default: return super.onOptionsItemSelected(item); } return super.onOptionsItemSelected(item); } /** Loading Loading
app/src/main/AndroidManifest.xml +3 −1 Original line number Diff line number Diff line Loading @@ -13,7 +13,9 @@ <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- For writing camera images API <= 28 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" tools:ignore="ScopedStorage" /> <!-- For auto start --> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <!-- For web sync retries --> Loading
app/src/main/java/net/fabiszewski/ulogger/ImageTask.java +57 −16 Original line number Diff line number Diff line Loading @@ -13,16 +13,20 @@ import android.app.Activity; import android.content.SharedPreferences; import android.graphics.Bitmap; import android.net.Uri; import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.annotation.WorkerThread; import androidx.preference.PreferenceManager; import java.io.IOException; import java.lang.ref.WeakReference; import static net.fabiszewski.ulogger.ImageHelper.clearImageCache; import static net.fabiszewski.ulogger.ImageHelper.getPersistablePermission; import static net.fabiszewski.ulogger.ImageHelper.getResampledBitmap; import static net.fabiszewski.ulogger.ImageHelper.getThumbnail; Loading @@ -32,26 +36,53 @@ import static net.fabiszewski.ulogger.ImageHelper.saveToCache; /** * Task to downsample image */ class ImageTask extends AsyncTask<Uri, Void, ImageTask.ImageTaskResult> { class ImageTask implements Runnable { private static final String TAG = ImageTask.class.getSimpleName(); private final WeakReference<ImageTaskCallback> weakCallback; private String errorMessage = "Image resampling failed"; private String errorMessage = ""; private final Uri uri; ImageTask(ImageTaskCallback callback) { private boolean isRunning = false; private boolean isCancelled = false; private final Handler uiHandler = new Handler(Looper.getMainLooper()); ImageTask(Uri uri, ImageTaskCallback callback) { this.uri = uri; weakCallback = new WeakReference<>(callback); } @Override protected ImageTaskResult doInBackground(Uri... params) { public void run() { isRunning = true; ImageTaskResult result = doInBackground(); if (isCancelled) { cleanUp(result); } else { uiHandler.post(() -> onPostExecute(result)); } isRunning = false; } public void cancel() { if (Logger.DEBUG) { Log.d(TAG, "[task cancelled]"); } isCancelled = true; } public boolean isRunning() { return isRunning; } @WorkerThread private ImageTaskResult doInBackground() { if (Logger.DEBUG) { Log.d(TAG, "[doInBackground]"); } Activity activity = getActivity(); if (activity == null || params.length != 1 || params[0] == null) { if (activity == null) { return null; } Uri inUri = params[0]; ImageTaskResult result = null; try { Uri savedUri; Loading @@ -60,11 +91,11 @@ class ImageTask extends AsyncTask<Uri, Void, ImageTask.ImageTaskResult> { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity); int dstWidth = Integer.parseInt(prefs.getString(SettingsActivity.KEY_IMAGE_SIZE, activity.getString(R.string.pref_imagesize_default))); if (dstWidth == 0) { savedUri = inUri; getPersistablePermission(activity, inUri); thumbnail = getThumbnail(activity, inUri); savedUri = uri; getPersistablePermission(activity, uri); thumbnail = getThumbnail(activity, uri); } else { Bitmap bitmap = getResampledBitmap(activity, inUri, dstWidth); Bitmap bitmap = getResampledBitmap(activity, uri, dstWidth); savedUri = saveToCache(activity, bitmap); thumbnail = getThumbnail(activity, bitmap); bitmap.recycle(); Loading @@ -74,17 +105,16 @@ class ImageTask extends AsyncTask<Uri, Void, ImageTask.ImageTaskResult> { } } catch (IOException e) { if (e.getMessage() != null) { errorMessage += ": " + e.getMessage(); errorMessage = e.getMessage(); } } return result; } @Override protected void onPostExecute(@Nullable ImageTaskResult result) { super.onPostExecute(result); @UiThread private void onPostExecute(@Nullable ImageTaskResult result) { ImageTaskCallback callback = weakCallback.get(); if (callback != null) { if (callback != null && callback.getActivity() != null) { if (result == null) { callback.onImageTaskFailure(errorMessage); } else { Loading @@ -102,6 +132,17 @@ class ImageTask extends AsyncTask<Uri, Void, ImageTask.ImageTaskResult> { return null; } /** * Try to clean image cache * @param result Task result */ private void cleanUp(ImageTaskResult result) { Activity activity = getActivity(); if (result != null && activity != null) { clearImageCache(activity.getApplicationContext()); } } interface ImageTaskCallback { void onImageTaskCompleted(@NonNull Uri uri, @NonNull Bitmap thumbnail); void onImageTaskFailure(@NonNull String error); Loading
app/src/main/java/net/fabiszewski/ulogger/LocationHelper.java +1 −1 Original line number Diff line number Diff line Loading @@ -167,6 +167,7 @@ class LocationHelper { * @param singleShot Request single update if true * @throws LoggerException Exception on permission denied or provider disabled */ @SuppressWarnings({"deprecation", "RedundantSuppression"}) private void requestProviderUpdates(@NonNull String provider, @NonNull LocationListener listener, @Nullable Looper looper, boolean singleShot) throws LoggerException { if (Logger.DEBUG) { Log.d(TAG, "[requestProviderUpdates: " + provider + " (" + singleShot + ")]"); } try { Loading @@ -174,7 +175,6 @@ class LocationHelper { // request even if provider is disabled to allow users re-enable it later locationManager.requestLocationUpdates(provider, minTimeMillis, minDistance, listener, looper); } else if (locationManager.isProviderEnabled(provider)) { // FIXME: original caller should be rewritten instead? if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { locationManager.getCurrentLocation(provider, null, runnable -> { if (looper != null) { Loading
app/src/main/java/net/fabiszewski/ulogger/LoggerTask.java +70 −38 Original line number Diff line number Diff line Loading @@ -12,13 +12,15 @@ package net.fabiszewski.ulogger; import android.app.Activity; import android.location.Location; import android.location.LocationListener; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.annotation.WorkerThread; import java.lang.ref.WeakReference; Loading @@ -26,7 +28,7 @@ import java.lang.ref.WeakReference; /** * Task to get location according to user preferences criteria */ class LoggerTask extends AsyncTask<Void, Void, Location> implements LocationListener { class LoggerTask implements LocationListener, Runnable { private static final String TAG = LoggerTask.class.getSimpleName(); private static final int E_OK = 0; Loading @@ -38,6 +40,11 @@ class LoggerTask extends AsyncTask<Void, Void, Location> implements LocationList private final LocationHelper locationHelper; private Location location; private volatile boolean waiting; private boolean isCancelled = false; private boolean isRunning = false; private final Handler uiHandler = new Handler(Looper.getMainLooper()); private final Object lock = new Object(); private int error = LocationHelper.LoggerException.E_OK; Loading @@ -47,7 +54,31 @@ class LoggerTask extends AsyncTask<Void, Void, Location> implements LocationList } @Override protected Location doInBackground(Void... voids) { public void run() { isRunning = true; Location location = doInBackground(); if (!isCancelled) { uiHandler.post(() -> onPostExecute(location)); } isRunning = false; } public void cancel() { if (Logger.DEBUG) { Log.d(TAG, "[task cancelled]"); } isCancelled = true; quitLoop(); } public boolean isRunning() { return isRunning; } /** * Requests location update, waits in loop * @return Location or null if none */ @WorkerThread private Location doInBackground() { if (Logger.DEBUG) { Log.d(TAG, "[doInBackground]"); } Activity activity = getActivity(); if (activity == null) { Loading @@ -57,52 +88,49 @@ class LoggerTask extends AsyncTask<Void, Void, Location> implements LocationList error = E_PERMISSION; return null; } synchronized (lock) { waiting = true; final long startTime = System.currentTimeMillis(); try { locationHelper.requestSingleUpdate(this, Looper.getMainLooper()); loop(); while (waiting) { try { lock.wait(TIMEOUT_MS); } catch (InterruptedException e) { if (Logger.DEBUG) { Log.d(TAG, "[loop interrupted]"); } } if (System.currentTimeMillis() - startTime >= TIMEOUT_MS) { if (Logger.DEBUG) { Log.d(TAG, "[loop timeout]"); } waiting = false; } } locationHelper.removeUpdates(this); return location; } catch (LocationHelper.LoggerException e) { error = e.getCode(); } } return null; } /** * Loop on worker thread * Quit loop */ private void loop() { waiting = true; final long startTime = System.currentTimeMillis(); while (waiting) { if (System.currentTimeMillis() - startTime > TIMEOUT_MS) { if (Logger.DEBUG) { Log.d(TAG, "[loop timeout]"); } break; } if (isCancelled()) { if (Logger.DEBUG) { Log.d(TAG, "[loop cancelled]"); } break; } try { Thread.sleep(500); } catch (InterruptedException ignored) { break; } private synchronized void quitLoop() { synchronized (lock) { waiting = false; lock.notifyAll(); } } /** * Quit loop * Execute callback with location result * @param location Location */ private void quitLoop() { waiting = false; } @Override protected void onPostExecute(Location location) { super.onPostExecute(location); @UiThread private void onPostExecute(Location location) { LoggerTaskCallback callback = weakCallback.get(); if (callback != null) { if (callback != null && callback.getActivity() != null) { if (error == E_OK && location != null) { callback.onLoggerTaskCompleted(location); } else { Loading Loading @@ -133,6 +161,7 @@ class LoggerTask extends AsyncTask<Void, Void, Location> implements LocationList } @UiThread @Override public void onLocationChanged(@NonNull Location location) { if (Logger.DEBUG) { Log.d(TAG, "[location changed: " + location + "]"); } Loading Loading @@ -161,10 +190,12 @@ class LoggerTask extends AsyncTask<Void, Void, Location> implements LocationList return true; } @SuppressWarnings({"deprecation", "RedundantSuppression"}) @UiThread @Override @SuppressWarnings({"deprecation", "RedundantSuppression"}) public void onStatusChanged(String provider, int status, Bundle extras) { } @UiThread @Override public void onProviderEnabled(@NonNull String provider) { if (Logger.DEBUG) { Log.d(TAG, "[onProviderEnabled: " + provider + "]"); } Loading @@ -175,6 +206,7 @@ class LoggerTask extends AsyncTask<Void, Void, Location> implements LocationList } } @UiThread @Override public void onProviderDisabled(@NonNull String provider) { if (Logger.DEBUG) { Log.d(TAG, "[onProviderDisabled: " + provider + "]"); } Loading
app/src/main/java/net/fabiszewski/ulogger/MainActivity.java +18 −21 Original line number Diff line number Diff line Loading @@ -130,28 +130,25 @@ public class MainActivity extends AppCompatActivity */ @Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { switch (item.getItemId()) { case R.id.menu_settings: final int id = item.getItemId(); if (id == R.id.menu_settings) { Intent i = new Intent(this, SettingsActivity.class); startActivityForResult(i, RESULT_PREFS_UPDATED); return true; case R.id.menu_about: } else if (id == R.id.menu_about) { showAbout(); return true; case R.id.menu_export: } else if (id == R.id.menu_export) { startExport(); return true; case R.id.menu_clear: } else if (id == R.id.menu_clear) { clearTrack(); return true; case android.R.id.home: } else if (id == android.R.id.home) { onBackPressed(); return true; default: return super.onOptionsItemSelected(item); } return super.onOptionsItemSelected(item); } /** Loading