forked from upstream-mirrors/OpenTracks
Export & import via Storage Access Framework.
This commit is contained in:
@@ -69,7 +69,7 @@ public class DescriptionGeneratorTest {
|
|||||||
track.setTripStatistics(stats);
|
track.setTripStatistics(stats);
|
||||||
track.setCategory("hiking");
|
track.setCategory("hiking");
|
||||||
String expected = //"Created by"
|
String expected = //"Created by"
|
||||||
"<a href='https://github.com/OpenTracksApp/opentracks'>OpenTracks</a><p>"
|
"<a href='https://github.com/OpenTracksApp/OpenTracks'>OpenTracks</a><p>"
|
||||||
+ "Name: -<br>"
|
+ "Name: -<br>"
|
||||||
+ "Activity type: hiking<br>"
|
+ "Activity type: hiking<br>"
|
||||||
+ "Description: -<br>"
|
+ "Description: -<br>"
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ limitations under the License.
|
|||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:icon="@drawable/ic_logo_color_24dp"
|
android:icon="@drawable/ic_logo_color_24dp"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:requestLegacyExternalStorage="true"
|
|
||||||
android:theme="@style/ThemeCustom">
|
android:theme="@style/ThemeCustom">
|
||||||
|
|
||||||
<!-- Activities -->
|
<!-- Activities -->
|
||||||
@@ -152,6 +151,4 @@ limitations under the License.
|
|||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -228,6 +228,7 @@ public class ContentProviderUtils {
|
|||||||
* @param dir the directory
|
* @param dir the directory
|
||||||
*/
|
*/
|
||||||
private void deleteDirectoryRecurse(Context context, File dir) {
|
private void deleteDirectoryRecurse(Context context, File dir) {
|
||||||
|
// TODO Does not work with
|
||||||
if (FileUtils.isDirectory(dir)) {
|
if (FileUtils.isDirectory(dir)) {
|
||||||
for (File child : dir.listFiles()) {
|
for (File child : dir.listFiles()) {
|
||||||
deleteDirectoryRecurse(context, child);
|
deleteDirectoryRecurse(context, child);
|
||||||
|
|||||||
@@ -16,21 +16,20 @@
|
|||||||
|
|
||||||
package de.dennisguse.opentracks.io.file.exporter;
|
package de.dennisguse.opentracks.io.file.exporter;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.app.ActivityCompat;
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.fragments.FileTypeDialogFragment;
|
import de.dennisguse.opentracks.fragments.FileTypeDialogFragment;
|
||||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||||
@@ -39,7 +38,6 @@ import de.dennisguse.opentracks.util.FileUtils;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* An activity for saving tracks to the external storage.
|
* An activity for saving tracks to the external storage.
|
||||||
* If saving a specific track, option to save it to a temp directory and play the track afterward.
|
|
||||||
*
|
*
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
@@ -53,34 +51,39 @@ public class ExportActivity extends FragmentActivity implements FileTypeDialogFr
|
|||||||
private String directoryDisplayName;
|
private String directoryDisplayName;
|
||||||
|
|
||||||
private ExportAsyncTask exportAsyncTask;
|
private ExportAsyncTask exportAsyncTask;
|
||||||
|
|
||||||
private ProgressDialog progressDialog;
|
private ProgressDialog progressDialog;
|
||||||
|
|
||||||
private int processedTrackCount;
|
private int processedTrackCount;
|
||||||
private int totalTrackCount;
|
private int totalTrackCount;
|
||||||
|
|
||||||
|
private Uri directoryUri;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||||
|
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||||
|
|
||||||
if (!FileUtils.isExternalStorageWriteable() || ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
|
startActivityForResult(intent, EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
|
||||||
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
|
|
||||||
} else {
|
|
||||||
fileTypeDialogStart();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent resultData) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, resultData);
|
||||||
if (requestCode == EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE) {
|
if (requestCode == EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE) {
|
||||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show();
|
// final int takeFlags = resultData.getFlags()
|
||||||
} else {
|
// & (Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
|
// | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||||
|
//// Check for the freshest data.
|
||||||
|
// getContentResolver().takePersistableUriPermission(uri, takeFlags);
|
||||||
|
directoryUri = resultData.getData();
|
||||||
fileTypeDialogStart();
|
fileTypeDialogStart();
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show();
|
||||||
|
finish();
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fileTypeDialogStart() {
|
private void fileTypeDialogStart() {
|
||||||
@@ -89,21 +92,14 @@ public class ExportActivity extends FragmentActivity implements FileTypeDialogFr
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFileTypeDone(TrackFileFormat trackFileFormat) {
|
public void onFileTypeDone(TrackFileFormat trackFileFormat) {
|
||||||
if (!FileUtils.isExternalStorageWriteable()) {
|
DocumentFile pickedDirectory = DocumentFile.fromTreeUri(this, directoryUri);
|
||||||
Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show();
|
DocumentFile exportDirectory = pickedDirectory.findFile(FileUtils.SDCARD_TOP_DIR);
|
||||||
finish();
|
if (exportDirectory == null) {
|
||||||
return;
|
exportDirectory = pickedDirectory.createDirectory(FileUtils.SDCARD_TOP_DIR);
|
||||||
}
|
|
||||||
|
|
||||||
File directory = new File(FileUtils.getPath(trackFileFormat.getExtension()));
|
|
||||||
if (!FileUtils.ensureDirectoryExists(directory)) {
|
|
||||||
Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show();
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
directoryDisplayName = FileUtils.getPathDisplayName(trackFileFormat.getExtension());
|
directoryDisplayName = FileUtils.getPathDisplayName(trackFileFormat.getExtension());
|
||||||
exportAsyncTask = new ExportAsyncTask(this, trackFileFormat, directory);
|
exportAsyncTask = new ExportAsyncTask(this, trackFileFormat, exportDirectory);
|
||||||
exportAsyncTask.execute();
|
exportAsyncTask.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,16 +22,16 @@ import android.os.AsyncTask;
|
|||||||
import android.os.PowerManager.WakeLock;
|
import android.os.PowerManager.WakeLock;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.File;
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||||
import de.dennisguse.opentracks.util.FileUtils;
|
|
||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.SystemUtils;
|
import de.dennisguse.opentracks.util.SystemUtils;
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
|
|
||||||
private static final String TAG = ExportAsyncTask.class.getSimpleName();
|
private static final String TAG = ExportAsyncTask.class.getSimpleName();
|
||||||
private final TrackFileFormat trackFileFormat;
|
private final TrackFileFormat trackFileFormat;
|
||||||
private final File directory;
|
private final DocumentFile directory;
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final ContentProviderUtils contentProviderUtils;
|
private final ContentProviderUtils contentProviderUtils;
|
||||||
private ExportActivity exportActivity;
|
private ExportActivity exportActivity;
|
||||||
@@ -63,7 +63,7 @@ public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
* @param trackFileFormat the track file format
|
* @param trackFileFormat the track file format
|
||||||
* @param directory the directory to write the file
|
* @param directory the directory to write the file
|
||||||
*/
|
*/
|
||||||
public ExportAsyncTask(ExportActivity exportActivity, TrackFileFormat trackFileFormat, File directory) {
|
public ExportAsyncTask(ExportActivity exportActivity, TrackFileFormat trackFileFormat, DocumentFile directory) {
|
||||||
this.exportActivity = exportActivity;
|
this.exportActivity = exportActivity;
|
||||||
this.trackFileFormat = trackFileFormat;
|
this.trackFileFormat = trackFileFormat;
|
||||||
this.directory = directory;
|
this.directory = directory;
|
||||||
@@ -156,13 +156,13 @@ public class ExportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, tracks, trackExporterListener);
|
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, tracks, trackExporterListener);
|
||||||
|
|
||||||
Track track = tracks[0];
|
Track track = tracks[0];
|
||||||
String fileName = FileUtils.buildUniqueFileName(directory, track.getName(), trackFileFormat.getExtension());
|
|
||||||
File file = new File(directory, fileName);
|
|
||||||
|
|
||||||
try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
|
String fileName = track.getId() + "." + trackFileFormat.getExtension();
|
||||||
if (trackExporter.writeTrack(context, fileOutputStream)) {
|
DocumentFile file = directory.createFile(trackFileFormat.getMimeType(), fileName);
|
||||||
|
|
||||||
|
try (OutputStream outputStream = context.getContentResolver().openOutputStream(file.getUri())) {
|
||||||
|
if (trackExporter.writeTrack(context, outputStream)) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
if (!file.delete()) {
|
if (!file.delete()) {
|
||||||
|
|||||||
@@ -16,21 +16,20 @@
|
|||||||
|
|
||||||
package de.dennisguse.opentracks.io.file.importer;
|
package de.dennisguse.opentracks.io.file.importer;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.app.ActivityCompat;
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
import de.dennisguse.opentracks.fragments.FileTypeDialogFragment;
|
import de.dennisguse.opentracks.fragments.FileTypeDialogFragment;
|
||||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||||
@@ -38,8 +37,7 @@ import de.dennisguse.opentracks.util.DialogUtils;
|
|||||||
import de.dennisguse.opentracks.util.FileUtils;
|
import de.dennisguse.opentracks.util.FileUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An activity to import files from the external storage. Optionally to import
|
* An activity to import files from the external storage.
|
||||||
* one specific file.
|
|
||||||
*
|
*
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
@@ -50,70 +48,58 @@ public class ImportActivity extends FragmentActivity implements FileTypeDialogFr
|
|||||||
private static final int DIALOG_PROGRESS_ID = 0;
|
private static final int DIALOG_PROGRESS_ID = 0;
|
||||||
private static final int DIALOG_RESULT_ID = 1;
|
private static final int DIALOG_RESULT_ID = 1;
|
||||||
|
|
||||||
|
private String directoryDisplayName;
|
||||||
|
|
||||||
private ImportAsyncTask importAsyncTask;
|
private ImportAsyncTask importAsyncTask;
|
||||||
private ProgressDialog progressDialog;
|
private ProgressDialog progressDialog;
|
||||||
|
|
||||||
// the path on the external storage to import
|
|
||||||
private String directoryDisplayName;
|
|
||||||
|
|
||||||
private int importedTrackCount;
|
private int importedTrackCount;
|
||||||
|
|
||||||
private int totalTrackCount;
|
private int totalTrackCount;
|
||||||
|
|
||||||
|
private Uri directoryUri;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
if (!FileUtils.isExternalStorageWriteable() || ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
|
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||||
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
} else {
|
|
||||||
fileTypeDialogStart();
|
startActivityForResult(intent, EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent resultData) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, resultData);
|
||||||
if (requestCode == EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE) {
|
if (requestCode == EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE) {
|
||||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
Toast.makeText(this, R.string.external_storage_not_readable, Toast.LENGTH_LONG).show();
|
// final int takeFlags = resultData.getFlags()
|
||||||
} else {
|
// & (Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
|
// | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||||
|
//// Check for the freshest data.
|
||||||
|
// getContentResolver().takePersistableUriPermission(uri, takeFlags);
|
||||||
|
directoryUri = resultData.getData();
|
||||||
fileTypeDialogStart();
|
fileTypeDialogStart();
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show();
|
||||||
|
finish();
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void fileTypeDialogStart() {
|
private void fileTypeDialogStart() {
|
||||||
FileTypeDialogFragment.showDialog(getSupportFragmentManager(), R.string.import_selection_title, R.string.import_selection_option);
|
FileTypeDialogFragment.showDialog(getSupportFragmentManager(), R.string.import_selection_title, R.string.import_selection_option);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFileTypeDone(TrackFileFormat trackFileFormat) {
|
public void onFileTypeDone(TrackFileFormat trackFileFormat) {
|
||||||
if (!FileUtils.isExternalStorageAvailable()) {
|
DocumentFile pickedDirectory = DocumentFile.fromTreeUri(this, directoryUri);
|
||||||
Toast.makeText(this, R.string.external_storage_not_available, Toast.LENGTH_LONG).show();
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
directoryDisplayName = FileUtils.getPathDisplayName(trackFileFormat.getExtension());
|
directoryDisplayName = FileUtils.getPathDisplayName(trackFileFormat.getExtension());
|
||||||
String directoryPath = FileUtils.getPath(trackFileFormat.getExtension());
|
|
||||||
if (!FileUtils.isDirectory(new File(directoryPath))) {
|
|
||||||
Toast.makeText(this, getString(R.string.import_no_directory, directoryDisplayName), Toast.LENGTH_LONG)
|
|
||||||
.show();
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO (still needed?): getLastNonConfiguration instance returned ExportAsyncTask before
|
importAsyncTask = new ImportAsyncTask(this, trackFileFormat, pickedDirectory);
|
||||||
// Object retained = getLastNonConfigurationInstance();
|
|
||||||
// if (retained instanceof ImportAsyncTask) {
|
|
||||||
// importAsyncTask = (ImportAsyncTask) retained;
|
|
||||||
// importAsyncTask.setActivity(this);
|
|
||||||
// } else {
|
|
||||||
importAsyncTask = new ImportAsyncTask(this, trackFileFormat, directoryPath);
|
|
||||||
importAsyncTask.execute();
|
importAsyncTask.execute();
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -154,8 +140,7 @@ public class ImportActivity extends FragmentActivity implements FileTypeDialogFr
|
|||||||
} else {
|
} else {
|
||||||
iconId = R.drawable.ic_dialog_error_24dp;
|
iconId = R.drawable.ic_dialog_error_24dp;
|
||||||
titleId = R.string.generic_error_title;
|
titleId = R.string.generic_error_title;
|
||||||
message = getString(
|
message = getString(R.string.import_error, importedTrackCount, totalFiles, directoryDisplayName);
|
||||||
R.string.import_error, importedTrackCount, totalFiles, directoryDisplayName);
|
|
||||||
}
|
}
|
||||||
return new AlertDialog.Builder(this).setCancelable(true).setIcon(iconId)
|
return new AlertDialog.Builder(this).setCancelable(true).setIcon(iconId)
|
||||||
.setMessage(message).setOnCancelListener(new DialogInterface.OnCancelListener() {
|
.setMessage(message).setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||||
|
|||||||
@@ -22,9 +22,10 @@ import android.os.AsyncTask;
|
|||||||
import android.os.PowerManager.WakeLock;
|
import android.os.PowerManager.WakeLock;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.File;
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
|
|
||||||
private static final String TAG = ImportAsyncTask.class.getSimpleName();
|
private static final String TAG = ImportAsyncTask.class.getSimpleName();
|
||||||
private final TrackFileFormat trackFileFormat;
|
private final TrackFileFormat trackFileFormat;
|
||||||
private final String path;
|
private final DocumentFile directory;
|
||||||
// TODO Can be removed, or?
|
// TODO Can be removed, or?
|
||||||
private Context context;
|
private Context context;
|
||||||
// TODO Use weak reference
|
// TODO Use weak reference
|
||||||
@@ -66,12 +67,12 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
*
|
*
|
||||||
* @param importActivity the activity currently associated with this AsyncTask
|
* @param importActivity the activity currently associated with this AsyncTask
|
||||||
* @param trackFileFormat the track file format
|
* @param trackFileFormat the track file format
|
||||||
* @param path path to import GPX files
|
* @param directory path to import GPX files
|
||||||
*/
|
*/
|
||||||
public ImportAsyncTask(ImportActivity importActivity, TrackFileFormat trackFileFormat, String path) {
|
public ImportAsyncTask(ImportActivity importActivity, TrackFileFormat trackFileFormat, DocumentFile directory) {
|
||||||
this.importActivity = importActivity;
|
this.importActivity = importActivity;
|
||||||
this.trackFileFormat = trackFileFormat;
|
this.trackFileFormat = trackFileFormat;
|
||||||
this.path = path;
|
this.directory = directory;
|
||||||
context = importActivity.getApplicationContext();
|
context = importActivity.getApplicationContext();
|
||||||
|
|
||||||
completed = false;
|
completed = false;
|
||||||
@@ -99,39 +100,26 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
protected Boolean doInBackground(Void... params) {
|
* Gets a list of files.
|
||||||
try {
|
* If importAll is true, returns a list of the files under the path directory.
|
||||||
Thread.currentThread().setPriority(Thread.MAX_PRIORITY); // TODO Should be set to previous level at the end of this method.
|
* If importAll is false, returns a list containing just the path file.
|
||||||
// Get the wake lock if not recording or paused
|
*/
|
||||||
boolean isRecording = PreferencesUtils.isRecording(importActivity);
|
private static List<DocumentFile> getFiles(DocumentFile file, TrackFileFormat trackFileFormat) {
|
||||||
boolean isPaused = PreferencesUtils.isRecordingTrackPaused(importActivity);
|
List<DocumentFile> files = new ArrayList<>();
|
||||||
if (!isRecording || isPaused) {
|
|
||||||
wakeLock = SystemUtils.acquireWakeLock(importActivity, wakeLock);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<File> files = getFiles();
|
for (DocumentFile candidate : file.listFiles()) {
|
||||||
totalTrackCount = files.size();
|
if (!candidate.isDirectory()) {
|
||||||
if (totalTrackCount == 0) {
|
String extension = FileUtils.getExtension(candidate.getName());
|
||||||
return true;
|
if (trackFileFormat.getExtension().equals(extension)) {
|
||||||
}
|
files.add(candidate);
|
||||||
|
|
||||||
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))) {
|
} else {
|
||||||
importTrackCount++;
|
files.addAll(getFiles(candidate, trackFileFormat));
|
||||||
}
|
|
||||||
publishProgress(i + 1, totalTrackCount);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} finally {
|
|
||||||
if (wakeLock != null && wakeLock.isHeld()) {
|
|
||||||
wakeLock.release();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -159,16 +147,49 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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.
|
* Imports a file.
|
||||||
*
|
*
|
||||||
* @param file the file
|
* @param file the file
|
||||||
*/
|
*/
|
||||||
private boolean importFile(final File file) {
|
private boolean importFile(final DocumentFile file) {
|
||||||
TrackImporter trackImporter;
|
TrackImporter trackImporter;
|
||||||
if (trackFileFormat == TrackFileFormat.GPX) {
|
if (trackFileFormat == TrackFileFormat.GPX) {
|
||||||
trackImporter = new GpxFileTrackImporter(importActivity);
|
trackImporter = new GpxFileTrackImporter(importActivity);
|
||||||
} else { //KML or KMZ
|
} else {
|
||||||
|
//KML or KMZ
|
||||||
String extension = FileUtils.getExtension(file.getName());
|
String extension = FileUtils.getExtension(file.getName());
|
||||||
if (TrackFileFormat.KML_ONLY_TRACK.getExtension().equals(extension)) {
|
if (TrackFileFormat.KML_ONLY_TRACK.getExtension().equals(extension)) {
|
||||||
trackImporter = new KmlFileTrackImporter(importActivity, -1L);
|
trackImporter = new KmlFileTrackImporter(importActivity, -1L);
|
||||||
@@ -181,37 +202,12 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try (FileInputStream fileInputStream = new FileInputStream(file)) {
|
try (InputStream inputStream = context.getContentResolver().openInputStream(file.getUri())) {
|
||||||
trackId = trackImporter.importFile(fileInputStream);
|
trackId = trackImporter.importFile(inputStream);
|
||||||
return trackId != -1L;
|
return trackId != -1L;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "Unable to import file", e);
|
Log.e(TAG, "Unable to import file", e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a list of files.
|
|
||||||
* If importAll is true, returns a list of the files under the path directory.
|
|
||||||
* If importAll is false, returns a list containing just the path file.
|
|
||||||
*/
|
|
||||||
private List<File> getFiles() {
|
|
||||||
List<File> files = new ArrayList<>();
|
|
||||||
File file = new File(path);
|
|
||||||
|
|
||||||
File[] candidates = file.listFiles();
|
|
||||||
if (candidates == null) {
|
|
||||||
return files;
|
|
||||||
}
|
|
||||||
for (File candidate : candidates) {
|
|
||||||
if (!FileUtils.isDirectory(candidate)) {
|
|
||||||
String extension = FileUtils.getExtension(candidate.getName());
|
|
||||||
if (trackFileFormat.getExtension().equals(extension)) {
|
|
||||||
files.add(candidate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return files;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public class FileUtils {
|
|||||||
/**
|
/**
|
||||||
* Our external storage directory.
|
* Our external storage directory.
|
||||||
*/
|
*/
|
||||||
static final String SDCARD_TOP_DIR = "OpenTracks";
|
public static final String SDCARD_TOP_DIR = "OpenTracks"; // TODO RENAME
|
||||||
private static final String TRACK_PREFIX = "OpenTracks";
|
private static final String TRACK_PREFIX = "OpenTracks";
|
||||||
/**
|
/**
|
||||||
* The maximum FAT32 path length. See the FAT32 spec at
|
* The maximum FAT32 path length. See the FAT32 spec at
|
||||||
@@ -54,15 +54,6 @@ public class FileUtils {
|
|||||||
private FileUtils() {
|
private FileUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the external storage is available.
|
|
||||||
*/
|
|
||||||
public static boolean isExternalStorageAvailable() {
|
|
||||||
String state = Environment.getExternalStorageState();
|
|
||||||
return Environment.MEDIA_MOUNTED.equals(state)
|
|
||||||
|| Environment.MEDIA_MOUNTED_READ_ONLY.equals(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the external storage is writable.
|
* Returns true if the external storage is writable.
|
||||||
*/
|
*/
|
||||||
@@ -76,6 +67,7 @@ public class FileUtils {
|
|||||||
*
|
*
|
||||||
* @param dir the directory
|
* @param dir the directory
|
||||||
*/
|
*/
|
||||||
|
@Deprecated // TODO REMOVE" rewrite all locations this was used.
|
||||||
public static boolean isDirectory(File dir) {
|
public static boolean isDirectory(File dir) {
|
||||||
return dir.exists() && dir.isDirectory();
|
return dir.exists() && dir.isDirectory();
|
||||||
}
|
}
|
||||||
@@ -86,6 +78,7 @@ public class FileUtils {
|
|||||||
* @return whether the directory exists (either already existed or was
|
* @return whether the directory exists (either already existed or was
|
||||||
* successfully created)
|
* successfully created)
|
||||||
*/
|
*/
|
||||||
|
@Deprecated // TODO REMOVE" rewrite all locations this was used.
|
||||||
public static boolean ensureDirectoryExists(File dir) {
|
public static boolean ensureDirectoryExists(File dir) {
|
||||||
if (isDirectory(dir)) {
|
if (isDirectory(dir)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -93,10 +86,12 @@ public class FileUtils {
|
|||||||
return dir.mkdirs();
|
return dir.mkdirs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated // TODO REMOVE" rewrite all locations this was used.
|
||||||
public static File getPhotoDir() {
|
public static File getPhotoDir() {
|
||||||
return new File(getPath(PICTURES_DIR));
|
return new File(getPath(PICTURES_DIR));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated // TODO REMOVE" rewrite all locations this was used.
|
||||||
public static File getPhotoDir(long trackId) {
|
public static File getPhotoDir(long trackId) {
|
||||||
return new File(getPath(PICTURES_DIR, TRACK_PREFIX + trackId));
|
return new File(getPath(PICTURES_DIR, TRACK_PREFIX + trackId));
|
||||||
}
|
}
|
||||||
@@ -122,6 +117,7 @@ public class FileUtils {
|
|||||||
*
|
*
|
||||||
* @param components the components
|
* @param components the components
|
||||||
*/
|
*/
|
||||||
|
@Deprecated // TODO REMOVE" rewrite all locations this was used.
|
||||||
public static String getPath(String... components) {
|
public static String getPath(String... components) {
|
||||||
return Environment.getExternalStorageDirectory() + getPathDisplayName(components);
|
return Environment.getExternalStorageDirectory() + getPathDisplayName(components);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -805,8 +805,7 @@ limitations under the License.
|
|||||||
dialog when there is no files to import.
|
dialog when there is no files to import.
|
||||||
|
|
||||||
%1$s the directory. E.g., '/kml'.">
|
%1$s the directory. E.g., '/kml'.">
|
||||||
There were no files to import. Connect your device to your computer and place the files to
|
There were no files to import. Connect your device to your computer and place the files to import in %1$s.
|
||||||
import in %1$s.
|
|
||||||
</string>
|
</string>
|
||||||
<string name="import_no_file_title" translation_description="When importing from the external storage, the title in the result
|
<string name="import_no_file_title" translation_description="When importing from the external storage, the title in the result
|
||||||
dialog when there is no files to import.">
|
dialog when there is no files to import.">
|
||||||
|
|||||||
Reference in New Issue
Block a user