Import: replaced async task with thread.

Extracted directory chooser into a separate activity.
This commit is contained in:
Dennis Guse
2020-07-07 21:33:50 +02:00
parent 4ded3c6bc8
commit 41a4a5527a
11 changed files with 174 additions and 246 deletions
@@ -31,6 +31,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
@@ -72,6 +73,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
// The current element content
//TODO Should be made private and getter be used by child classes.
protected String content;
protected String icon;
protected String name;
protected String description;
@@ -83,6 +85,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
protected String speed;
protected String waypointType;
protected String photoUrl;
protected String uuid;
// The current track data
private TrackData trackData;
@@ -91,11 +94,11 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
private Locator locator;
/**
* Constructor.
*
* @param context the context
* @param importTrackId the track id to import to. -1L to import to a new track.
*/
@Deprecated
// Remove importTrackId
AbstractFileTrackImporter(Context context, long importTrackId, ContentProviderUtils contentProviderUtils) {
this.context = context;
this.importTrackId = importTrackId;
@@ -244,6 +247,16 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
if (name != null) {
trackData.track.setName(name);
}
UUID uuidParsed;
try {
uuidParsed = UUID.fromString(uuid);
} catch (IllegalArgumentException | NullPointerException e) {
Log.w(TAG, "could not parse Track UUID, generating a new one.");
uuidParsed = UUID.randomUUID();
}
trackData.track.setUuid(uuidParsed);
if (description != null) {
trackData.track.setDescription(description);
}
@@ -0,0 +1,41 @@
package de.dennisguse.opentracks.io.file.importer;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import de.dennisguse.opentracks.util.IntentUtils;
public class DirectoryChooserActivity extends AppCompatActivity {
private static final int DIRECTORY_PICKER_REQUEST_CODE = 6;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(intent, DIRECTORY_PICKER_REQUEST_CODE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent resultData) {
super.onActivityResult(requestCode, resultCode, resultData);
if (requestCode == DIRECTORY_PICKER_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
Uri directoryUri = resultData.getData();
Intent intent = IntentUtils.newIntent(this, ImportActivity.class);
intent.putExtra(ImportActivity.EXTRA_DIRECTORY_URI_KEY, directoryUri);
startActivity(intent);
}
finish();
}
}
}
@@ -16,24 +16,24 @@
package de.dennisguse.opentracks.io.file.importer;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.documentfile.provider.DocumentFile;
import androidx.fragment.app.FragmentActivity;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.util.DialogUtils;
import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
/**
* An activity to import files from the external storage.
@@ -42,14 +42,18 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
*/
public class ImportActivity extends FragmentActivity {
private static final int DIRECTORY_PICKER_REQUEST_CODE = 6;
private static final String TAG = ImportActivity.class.getSimpleName();
public static final String EXTRA_DIRECTORY_URI_KEY = "directory_uri";
private static final int DIALOG_PROGRESS_ID = 0;
private static final int DIALOG_RESULT_ID = 1;
private DocumentFile pickedDirectory;
private String directoryDisplayName;
private ImportAsyncTask importAsyncTask;
private Thread importTask = new ImportThread();
private ProgressDialog progressDialog;
private int importedTrackCount;
@@ -59,29 +63,22 @@ public class ImportActivity extends FragmentActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(intent, DIRECTORY_PICKER_REQUEST_CODE);
Uri directoryUri = getIntent().getParcelableExtra(EXTRA_DIRECTORY_URI_KEY);
pickedDirectory = DocumentFile.fromTreeUri(this, directoryUri);
directoryDisplayName = FileUtils.getPath(pickedDirectory);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent resultData) {
super.onActivityResult(requestCode, resultCode, resultData);
if (requestCode == DIRECTORY_PICKER_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
Uri directoryUri = resultData.getData();
DocumentFile pickedDirectory = DocumentFile.fromTreeUri(this, directoryUri);
protected void onStart() {
super.onStart();
showDialog(DIALOG_PROGRESS_ID);
importTask.start();
}
directoryDisplayName = FileUtils.getPath(pickedDirectory);
TrackFileFormat trackFileFormat = PreferencesUtils.getExportTrackFileFormat(this);
importAsyncTask = new ImportAsyncTask(this, trackFileFormat, pickedDirectory);
importAsyncTask.execute();
} else {
finish();
}
}
@Override
protected void onPause() {
super.onPause();
importTask.interrupt();
}
@Override
@@ -89,13 +86,10 @@ public class ImportActivity extends FragmentActivity {
switch (id) {
case DIALOG_PROGRESS_ID:
progressDialog = DialogUtils.createHorizontalProgressDialog(
this, R.string.import_progress_message, new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
importAsyncTask.cancel(true);
dialog.dismiss();
finish();
}
this, R.string.import_progress_message, dialog -> {
importTask.interrupt();
dialog.dismiss();
finish();
}, directoryDisplayName);
return progressDialog;
case DIALOG_RESULT_ID:
@@ -120,18 +114,12 @@ public class ImportActivity extends FragmentActivity {
message = getString(R.string.import_error, importedTrackCount, totalFiles, directoryDisplayName);
}
return new AlertDialog.Builder(this).setCancelable(true).setIcon(iconId)
.setMessage(message).setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialogInterface) {
dialogInterface.dismiss();
finish();
}
}).setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int which) {
dialogInterface.dismiss();
finish();
}
.setMessage(message).setOnCancelListener(dialogInterface -> {
dialogInterface.dismiss();
finish();
}).setPositiveButton(R.string.generic_ok, (dialogInterface, which) -> {
dialogInterface.dismiss();
finish();
}).setTitle(titleId).create();
default:
return null;
@@ -145,17 +133,12 @@ public class ImportActivity extends FragmentActivity {
* @param aTotalCount the number of files to import
*/
public void onAsyncTaskCompleted(int aSuccessCount, int aTotalCount) {
importedTrackCount = aSuccessCount;
totalTrackCount = aTotalCount;
removeDialog(DIALOG_PROGRESS_ID);
showDialog(DIALOG_RESULT_ID);
}
/**
* Shows the progress dialog.
*/
public void showProgressDialog() {
showDialog(DIALOG_PROGRESS_ID);
runOnUiThread(() -> {
importedTrackCount = aSuccessCount;
totalTrackCount = aTotalCount;
removeDialog(DIALOG_PROGRESS_ID);
showDialog(DIALOG_RESULT_ID);
});
}
/**
@@ -171,4 +154,54 @@ public class ImportActivity extends FragmentActivity {
progressDialog.setProgress(Math.min(number, max));
}
}
public class ImportThread extends Thread {
@Override
public void run() {
List<DocumentFile> files = FileUtils.getFiles(pickedDirectory);
int totalTrackCount = files.size();
int importedTrackCount = 0;
for (int i = 0; i < totalTrackCount; i++) {
if (Thread.interrupted()) {
return;
}
if (importFile(files.get(i))) {
importedTrackCount++;
}
setProgressDialogValue(i + 1, totalTrackCount);
}
onAsyncTaskCompleted(importedTrackCount, totalTrackCount);
}
/**
* Imports a file.
*
* @param file the file
*/
private boolean importFile(final DocumentFile file) {
TrackImporter trackImporter;
String fileExtension = FileUtils.getExtension(file);
if (TrackFileFormat.GPX.getExtension().equals(fileExtension)) {
trackImporter = new GpxFileTrackImporter(ImportActivity.this);
} else if (TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getExtension().equals(fileExtension)) {
trackImporter = new KmlFileTrackImporter(ImportActivity.this, -1L);
} else if (TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getExtension().equals(fileExtension)) {
trackImporter = new KmzTrackImporter(ImportActivity.this, -1L, file.getUri());
} else {
Log.i(TAG, "Unsupported file format.");
return false;
}
try (InputStream inputStream = ImportActivity.this.getContentResolver().openInputStream(file.getUri())) {
return trackImporter.importFile(inputStream) != -1L;
} catch (IOException e) {
Log.e(TAG, "Unable to import file", e);
return false;
}
}
}
}
@@ -1,179 +0,0 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.io.file.importer;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import androidx.documentfile.provider.DocumentFile;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.SystemUtils;
/**
* AsyncTask to import files from the external storage.
*
* @author Jimmy Shih
*/
class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
private static final String TAG = ImportAsyncTask.class.getSimpleName();
private final TrackFileFormat trackFileFormat;
private final DocumentFile directory;
// TODO Use weak reference
private ImportActivity importActivity;
private WakeLock wakeLock;
private int importTrackCount;
private int totalTrackCount;
private long lastSuccessfulTrackId;
public ImportAsyncTask(ImportActivity importActivity, TrackFileFormat trackFileFormat, DocumentFile directory) {
this.importActivity = importActivity;
this.trackFileFormat = trackFileFormat;
this.directory = directory;
importTrackCount = 0;
totalTrackCount = 0;
lastSuccessfulTrackId = -1L;
}
@Override
protected void onPreExecute() {
if (importActivity != null) {
importActivity.showProgressDialog();
}
}
/**
* Gets a list of files.
*/
private static List<DocumentFile> getFiles(DocumentFile file, TrackFileFormat trackFileFormat) {
List<DocumentFile> files = new ArrayList<>();
for (DocumentFile candidate : file.listFiles()) {
if (!candidate.isDirectory()) {
String extension = FileUtils.getExtension(candidate.getName());
if (extension != null && trackFileFormat.getExtension().equals(extension)) {
files.add(candidate);
}
} else {
files.addAll(getFiles(candidate, trackFileFormat));
}
}
return files;
}
@Override
protected void onProgressUpdate(Integer... values) {
if (importActivity != null) {
importActivity.setProgressDialogValue(values[0], values[1]);
}
}
@Override
protected void onPostExecute(Boolean result) {
if (importActivity != null) {
importActivity.onAsyncTaskCompleted(importTrackCount, totalTrackCount);
}
}
@Override
protected void onCancelled() {
if (importActivity != null) {
importActivity.onAsyncTaskCompleted(importTrackCount, totalTrackCount);
importActivity = null;
}
}
@Override
protected Boolean doInBackground(Void... params) {
try {
Thread.currentThread().setPriority(Thread.MAX_PRIORITY); // TODO Should be set to previous level at the end of this method.
// Get the wake lock if not recording or paused
boolean isRecording = PreferencesUtils.isRecording(importActivity);
boolean isPaused = PreferencesUtils.isRecordingTrackPaused(importActivity);
if (!isRecording || isPaused) {
wakeLock = SystemUtils.acquireWakeLock(importActivity, wakeLock);
}
List<DocumentFile> files = getFiles(directory, trackFileFormat);
totalTrackCount = files.size();
for (int i = 0; i < totalTrackCount; i++) {
if (isCancelled()) {
// If cancelled, return true to show the number of files imported
return true;
}
if (importFile(files.get(i))) {
importTrackCount++;
}
publishProgress(i + 1, totalTrackCount);
}
return true;
} finally {
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
}
}
}
/**
* Imports a file.
*
* @param file the file
*/
private boolean importFile(final DocumentFile file) {
TrackImporter trackImporter;
if (trackFileFormat == TrackFileFormat.GPX) {
trackImporter = new GpxFileTrackImporter(importActivity);
} else {
//KML or KMZ
String extension = FileUtils.getExtension(file.getName());
if (TrackFileFormat.KML_ONLY_TRACK.getExtension().equals(extension)) {
trackImporter = new KmlFileTrackImporter(importActivity, -1L);
} else {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(importActivity);
Uri uri = contentProviderUtils.insertTrack(new Track());
long newId = Long.parseLong(uri.getLastPathSegment());
trackImporter = new KmzTrackImporter(importActivity, newId, file.getUri());
}
}
try (InputStream inputStream = importActivity.getContentResolver().openInputStream(file.getUri())) {
lastSuccessfulTrackId = trackImporter.importFile(inputStream);
return lastSuccessfulTrackId != -1L;
} catch (IOException e) {
Log.e(TAG, "Unable to import file", e);
return false;
}
}
}
@@ -72,7 +72,6 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
* Constructor.
*
* @param context the context
* @param importTrackId track id to import to. -1L to import to a new track.
*/
public KmlFileTrackImporter(Context context, long importTrackId) {
this(context, importTrackId, new ContentProviderUtils(context));
@@ -56,8 +56,6 @@ public class KmzTrackImporter implements TrackImporter {
private Uri uriKmzFile;
/**
* Constructor.
*
* @param context the context
* @param importTrackId track id to import to. This should not be -1L so that images in the kmz file can be imported.
* @param uriFile URI of the kmz file.
@@ -30,5 +30,6 @@ interface TrackImporter {
* @param inputStream the file's input stream
* @return the imported track id or RECORDING_TRACK_ID_DEFAULT.
*/
//TODO Figure out how can make the import an atomic operation (incl. database transaction rollback).
long importFile(InputStream inputStream);
}
@@ -29,6 +29,7 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import de.dennisguse.opentracks.BuildConfig;
@@ -111,6 +112,10 @@ public class FileUtils {
return fileName.substring(index + 1);
}
public static String getExtension(DocumentFile file) {
return getExtension(file.getName());
}
/**
* Builds a filename with the given base and the given extension, possibly adding a suffix to ensure the file doesn't exist.
*
@@ -229,13 +234,13 @@ public class FileUtils {
return file.getAbsolutePath();
}
/**
/**
* Copy a File (src) to a File (dst).
*
* @param src source file.
* @param dst destination file.
*/
public static void copy(FileDescriptor src, File dst) {
*/
public static void copy(FileDescriptor src, File dst) {
try (FileChannel in = new FileInputStream(src).getChannel();
FileChannel out = new FileOutputStream(dst).getChannel()) {
in.transferTo(0, in.size(), out);
@@ -262,7 +267,7 @@ public class FileUtils {
* @param context the Context.
* @param trackId the id of the Track.
* @param uri the uri to check.
* @return File object or null.
* @return File object or null.
*/
public static File getPhotoFileIfExists(Context context, long trackId, Uri uri) {
if (uri == null) {
@@ -300,4 +305,18 @@ public class FileUtils {
file.delete();
}
}
public static ArrayList<DocumentFile> getFiles(DocumentFile file) {
ArrayList<DocumentFile> files = new ArrayList<>();
for (DocumentFile candidate : file.listFiles()) {
if (!candidate.isDirectory()) {
files.add(candidate);
} else {
files.addAll(getFiles(candidate));
}
}
return files;
}
}