mirror of
https://github.com/CatimaLoyalty/Android.git
synced 2025-12-24 15:47:53 -05:00
Compare commits
1 Commits
create-pul
...
importExpo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd5ef267e8 |
@@ -94,6 +94,7 @@ dependencies {
|
||||
implementation("androidx.preference:preference:1.2.1")
|
||||
implementation("com.google.android.material:material:1.12.0")
|
||||
implementation("com.github.yalantis:ucrop:2.2.9")
|
||||
implementation("androidx.work:work-runtime:2.9.0")
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
|
||||
|
||||
// Splash Screen
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
<uses-sdk tools:overrideLibrary="com.google.zxing.client.android" />
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="23" />
|
||||
|
||||
<uses-feature
|
||||
@@ -188,5 +190,6 @@
|
||||
<action android:name="android.service.controls.ControlsProviderService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service android:name=".importexport.ImportExportWorker"/>
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
@@ -17,31 +18,31 @@ import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.work.Data;
|
||||
import androidx.work.ExistingWorkPolicy;
|
||||
import androidx.work.OneTimeWorkRequest;
|
||||
import androidx.work.OutOfQuotaPolicy;
|
||||
import androidx.work.WorkManager;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import protect.card_locker.async.TaskHandler;
|
||||
import protect.card_locker.databinding.ImportExportActivityBinding;
|
||||
import protect.card_locker.importexport.DataFormat;
|
||||
import protect.card_locker.importexport.ImportExportResult;
|
||||
import protect.card_locker.importexport.ImportExportResultType;
|
||||
import protect.card_locker.importexport.ImportExportWorker;
|
||||
|
||||
public class ImportExportActivity extends CatimaAppCompatActivity {
|
||||
private ImportExportActivityBinding binding;
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
private ImportExportTask importExporter;
|
||||
|
||||
private String importAlertTitle;
|
||||
private String importAlertMessage;
|
||||
private DataFormat importDataFormat;
|
||||
@@ -51,7 +52,10 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
|
||||
private ActivityResultLauncher<String> fileOpenLauncher;
|
||||
private ActivityResultLauncher<Intent> filePickerLauncher;
|
||||
|
||||
final private TaskHandler mTasks = new TaskHandler();
|
||||
private static final int PERMISSION_REQUEST_EXPORT = 100;
|
||||
private static final int PERMISSION_REQUEST_IMPORT = 101;
|
||||
|
||||
private OneTimeWorkRequest mRequestedWorkRequest;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -80,15 +84,20 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
|
||||
Log.e(TAG, "Activity returned NULL uri");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
OutputStream writer = getContentResolver().openOutputStream(uri);
|
||||
Log.e(TAG, "Starting file export with: " + result.toString());
|
||||
startExport(writer, uri, exportPassword.toCharArray(), true);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to export file: " + result.toString(), e);
|
||||
onExportComplete(new ImportExportResult(ImportExportResultType.GenericFailure, result.toString()), uri);
|
||||
}
|
||||
|
||||
Data exportRequestData = new Data.Builder()
|
||||
.putString(ImportExportWorker.INPUT_URI, uri.toString())
|
||||
.putString(ImportExportWorker.INPUT_ACTION, ImportExportWorker.ACTION_EXPORT)
|
||||
.putString(ImportExportWorker.INPUT_FORMAT, DataFormat.Catima.name())
|
||||
.putString(ImportExportWorker.INPUT_PASSWORD, exportPassword)
|
||||
.build();
|
||||
|
||||
mRequestedWorkRequest = new OneTimeWorkRequest.Builder(ImportExportWorker.class)
|
||||
.setInputData(exportRequestData)
|
||||
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
||||
.build();
|
||||
|
||||
PermissionUtils.requestPostNotificationsPermission(this, PERMISSION_REQUEST_EXPORT);
|
||||
});
|
||||
fileOpenLauncher = registerForActivityResult(new ActivityResultContracts.GetContent(), result -> {
|
||||
if (result == null) {
|
||||
@@ -159,15 +168,24 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
|
||||
importApplication.setOnClickListener(v -> chooseImportType(true, null));
|
||||
}
|
||||
|
||||
public static OneTimeWorkRequest buildImportRequest(DataFormat dataFormat, Uri uri, char[] password) {
|
||||
Data importRequestData = new Data.Builder()
|
||||
.putString(ImportExportWorker.INPUT_URI, uri.toString())
|
||||
.putString(ImportExportWorker.INPUT_ACTION, ImportExportWorker.ACTION_IMPORT)
|
||||
.putString(ImportExportWorker.INPUT_FORMAT, dataFormat.name())
|
||||
.putString(ImportExportWorker.INPUT_PASSWORD, Arrays.toString(password))
|
||||
.build();
|
||||
|
||||
return new OneTimeWorkRequest.Builder(ImportExportWorker.class)
|
||||
.setInputData(importRequestData)
|
||||
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void openFileForImport(Uri uri, char[] password) {
|
||||
try {
|
||||
InputStream reader = getContentResolver().openInputStream(uri);
|
||||
Log.e(TAG, "Starting file import with: " + uri.toString());
|
||||
startImport(reader, uri, importDataFormat, password, true);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to import file: " + uri.toString(), e);
|
||||
onImportComplete(new ImportExportResult(ImportExportResultType.GenericFailure, e.toString()), uri, importDataFormat);
|
||||
}
|
||||
mRequestedWorkRequest = buildImportRequest(importDataFormat, uri, password);
|
||||
|
||||
PermissionUtils.requestPostNotificationsPermission(this, PERMISSION_REQUEST_IMPORT);
|
||||
}
|
||||
|
||||
private void chooseImportType(boolean choosePicker,
|
||||
@@ -232,20 +250,17 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
.setTitle(importAlertTitle)
|
||||
.setMessage(importAlertMessage)
|
||||
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
try {
|
||||
if (choosePicker) {
|
||||
final Intent intentPickAction = new Intent(Intent.ACTION_PICK);
|
||||
filePickerLauncher.launch(intentPickAction);
|
||||
} else {
|
||||
fileOpenLauncher.launch("*/*");
|
||||
}
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(getApplicationContext(), R.string.failedOpeningFileManager, Toast.LENGTH_LONG).show();
|
||||
Log.e(TAG, "No activity found to handle intent", e);
|
||||
.setPositiveButton(R.string.ok, (dialog1, which1) -> {
|
||||
try {
|
||||
if (choosePicker) {
|
||||
final Intent intentPickAction = new Intent(Intent.ACTION_PICK);
|
||||
filePickerLauncher.launch(intentPickAction);
|
||||
} else {
|
||||
fileOpenLauncher.launch("*/*");
|
||||
}
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(getApplicationContext(), R.string.failedOpeningFileManager, Toast.LENGTH_LONG).show();
|
||||
Log.e(TAG, "No activity found to handle intent", e);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
@@ -254,60 +269,12 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void startImport(final InputStream target, final Uri targetUri, final DataFormat dataFormat, final char[] password, final boolean closeWhenDone) {
|
||||
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false);
|
||||
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() {
|
||||
@Override
|
||||
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat) {
|
||||
onImportComplete(result, targetUri, dataFormat);
|
||||
if (closeWhenDone) {
|
||||
try {
|
||||
target.close();
|
||||
} catch (IOException ioException) {
|
||||
ioException.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
importExporter = new ImportExportTask(ImportExportActivity.this,
|
||||
dataFormat, target, password, listener);
|
||||
mTasks.executeTask(TaskHandler.TYPE.IMPORT, importExporter);
|
||||
}
|
||||
|
||||
private void startExport(final OutputStream target, final Uri targetUri, char[] password, final boolean closeWhenDone) {
|
||||
mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false);
|
||||
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() {
|
||||
@Override
|
||||
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat) {
|
||||
onExportComplete(result, targetUri);
|
||||
if (closeWhenDone) {
|
||||
try {
|
||||
target.close();
|
||||
} catch (IOException ioException) {
|
||||
ioException.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
importExporter = new ImportExportTask(ImportExportActivity.this,
|
||||
DataFormat.Catima, target, password, listener);
|
||||
mTasks.executeTask(TaskHandler.TYPE.EXPORT, importExporter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, true, false, false);
|
||||
mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, true, false, false);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
|
||||
if (id == android.R.id.home) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
@@ -315,19 +282,19 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void retryWithPassword(DataFormat dataFormat, Uri uri) {
|
||||
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
|
||||
public static void retryWithPassword(Context context, DataFormat dataFormat, Uri uri) {
|
||||
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(context);
|
||||
builder.setTitle(R.string.passwordRequired);
|
||||
|
||||
FrameLayout container = new FrameLayout(ImportExportActivity.this);
|
||||
FrameLayout container = new FrameLayout(context);
|
||||
|
||||
final TextInputLayout textInputLayout = new TextInputLayout(ImportExportActivity.this);
|
||||
final TextInputLayout textInputLayout = new TextInputLayout(context);
|
||||
textInputLayout.setEndIconMode(TextInputLayout.END_ICON_PASSWORD_TOGGLE);
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
params.setMargins(50, 10, 50, 0);
|
||||
textInputLayout.setLayoutParams(params);
|
||||
|
||||
final EditText input = new EditText(ImportExportActivity.this);
|
||||
final EditText input = new EditText(context);
|
||||
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
input.setHint(R.string.exportPasswordHint);
|
||||
|
||||
@@ -336,75 +303,55 @@ public class ImportExportActivity extends CatimaAppCompatActivity {
|
||||
builder.setView(container);
|
||||
|
||||
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
|
||||
openFileForImport(uri, input.getText().toString().toCharArray());
|
||||
OneTimeWorkRequest importRequest = ImportExportActivity.buildImportRequest(dataFormat, uri, input.getText().toString().toCharArray());
|
||||
WorkManager.getInstance(context).enqueueUniqueWork(ImportExportWorker.ACTION_IMPORT, ExistingWorkPolicy.REPLACE, importRequest);
|
||||
});
|
||||
builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.cancel());
|
||||
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private String buildResultDialogMessage(ImportExportResult result, boolean isImport) {
|
||||
int messageId;
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
|
||||
if (result.resultType() == ImportExportResultType.Success) {
|
||||
messageId = isImport ? R.string.importSuccessful : R.string.exportSuccessful;
|
||||
} else {
|
||||
messageId = isImport ? R.string.importFailed : R.string.exportFailed;
|
||||
}
|
||||
|
||||
StringBuilder messageBuilder = new StringBuilder(getResources().getString(messageId));
|
||||
if (result.developerDetails() != null) {
|
||||
messageBuilder.append("\n\n");
|
||||
messageBuilder.append(getResources().getString(R.string.include_if_asking_support));
|
||||
messageBuilder.append("\n\n");
|
||||
messageBuilder.append(result.developerDetails());
|
||||
}
|
||||
|
||||
return messageBuilder.toString();
|
||||
onMockedRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
private void onImportComplete(ImportExportResult result, Uri path, DataFormat dataFormat) {
|
||||
ImportExportResultType resultType = result.resultType();
|
||||
public void onMockedRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
boolean granted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
||||
Integer failureReason = null;
|
||||
|
||||
if (resultType == ImportExportResultType.BadPassword) {
|
||||
retryWithPassword(dataFormat, path);
|
||||
return;
|
||||
if (requestCode == PERMISSION_REQUEST_EXPORT) {
|
||||
if (granted) {
|
||||
WorkManager.getInstance(this).enqueueUniqueWork(ImportExportWorker.ACTION_EXPORT, ExistingWorkPolicy.REPLACE, mRequestedWorkRequest);
|
||||
|
||||
Toast.makeText(this, R.string.exportStartedCheckNotifications, Toast.LENGTH_LONG).show();
|
||||
|
||||
// Import/export started
|
||||
setResult(RESULT_OK);
|
||||
finish();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
failureReason = R.string.postNotificationsPermissionRequired;
|
||||
} else if (requestCode == PERMISSION_REQUEST_IMPORT) {
|
||||
if (granted) {
|
||||
WorkManager.getInstance(this).enqueueUniqueWork(ImportExportWorker.ACTION_IMPORT, ExistingWorkPolicy.REPLACE, mRequestedWorkRequest);
|
||||
|
||||
// Import/export started
|
||||
setResult(RESULT_OK);
|
||||
finish();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
failureReason = R.string.postNotificationsPermissionRequired;
|
||||
}
|
||||
|
||||
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
|
||||
builder.setTitle(resultType == ImportExportResultType.Success ? R.string.importSuccessfulTitle : R.string.importFailedTitle);
|
||||
builder.setMessage(buildResultDialogMessage(result, true));
|
||||
builder.setNeutralButton(R.string.ok, (dialog, which) -> dialog.dismiss());
|
||||
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
private void onExportComplete(ImportExportResult result, final Uri path) {
|
||||
ImportExportResultType resultType = result.resultType();
|
||||
|
||||
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
|
||||
builder.setTitle(resultType == ImportExportResultType.Success ? R.string.exportSuccessfulTitle : R.string.exportFailedTitle);
|
||||
builder.setMessage(buildResultDialogMessage(result, false));
|
||||
builder.setNeutralButton(R.string.ok, (dialog, which) -> dialog.dismiss());
|
||||
|
||||
if (resultType == ImportExportResultType.Success) {
|
||||
final CharSequence sendLabel = ImportExportActivity.this.getResources().getText(R.string.sendLabel);
|
||||
|
||||
builder.setPositiveButton(sendLabel, (dialog, which) -> {
|
||||
Intent sendIntent = new Intent(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, path);
|
||||
sendIntent.setType("text/csv");
|
||||
|
||||
// set flag to give temporary permission to external app to use the FileProvider
|
||||
sendIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
|
||||
ImportExportActivity.this.startActivity(Intent.createChooser(sendIntent,
|
||||
sendLabel));
|
||||
|
||||
dialog.dismiss();
|
||||
});
|
||||
if (failureReason != null) {
|
||||
Toast.makeText(this, failureReason, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
builder.create().show();
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import protect.card_locker.async.CompatCallable;
|
||||
import protect.card_locker.importexport.DataFormat;
|
||||
import protect.card_locker.importexport.ImportExportResult;
|
||||
import protect.card_locker.importexport.ImportExportResultType;
|
||||
import protect.card_locker.importexport.MultiFormatExporter;
|
||||
import protect.card_locker.importexport.MultiFormatImporter;
|
||||
|
||||
public class ImportExportTask implements CompatCallable<ImportExportResult> {
|
||||
private static final String TAG = "Catima";
|
||||
|
||||
private Activity activity;
|
||||
private boolean doImport;
|
||||
private DataFormat format;
|
||||
private OutputStream outputStream;
|
||||
private InputStream inputStream;
|
||||
private char[] password;
|
||||
private TaskCompleteListener listener;
|
||||
|
||||
private ProgressDialog progress;
|
||||
|
||||
/**
|
||||
* Constructor which will setup a task for exporting to the given file
|
||||
*/
|
||||
ImportExportTask(Activity activity, DataFormat format, OutputStream output, char[] password,
|
||||
TaskCompleteListener listener) {
|
||||
super();
|
||||
this.activity = activity;
|
||||
this.doImport = false;
|
||||
this.format = format;
|
||||
this.outputStream = output;
|
||||
this.password = password;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor which will setup a task for importing from the given InputStream.
|
||||
*/
|
||||
ImportExportTask(Activity activity, DataFormat format, InputStream input, char[] password,
|
||||
TaskCompleteListener listener) {
|
||||
super();
|
||||
this.activity = activity;
|
||||
this.doImport = true;
|
||||
this.format = format;
|
||||
this.inputStream = input;
|
||||
this.password = password;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private ImportExportResult performImport(Context context, InputStream stream, SQLiteDatabase database, char[] password) {
|
||||
ImportExportResult importResult = MultiFormatImporter.importData(context, database, stream, format, password);
|
||||
|
||||
Log.i(TAG, "Import result: " + importResult);
|
||||
|
||||
return importResult;
|
||||
}
|
||||
|
||||
private ImportExportResult performExport(Context context, OutputStream stream, SQLiteDatabase database, char[] password) {
|
||||
ImportExportResult result;
|
||||
|
||||
try {
|
||||
OutputStreamWriter writer = new OutputStreamWriter(stream, StandardCharsets.UTF_8);
|
||||
result = MultiFormatExporter.exportData(context, database, stream, format, password);
|
||||
writer.close();
|
||||
} catch (IOException e) {
|
||||
result = new ImportExportResult(ImportExportResultType.GenericFailure, e.toString());
|
||||
Log.e(TAG, "Unable to export file", e);
|
||||
}
|
||||
|
||||
Log.i(TAG, "Export result: " + result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void onPreExecute() {
|
||||
progress = new ProgressDialog(activity);
|
||||
progress.setTitle(doImport ? R.string.importing : R.string.exporting);
|
||||
|
||||
progress.setOnDismissListener(new DialogInterface.OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
ImportExportTask.this.stop();
|
||||
}
|
||||
});
|
||||
|
||||
progress.show();
|
||||
}
|
||||
|
||||
protected ImportExportResult doInBackground(Void... nothing) {
|
||||
final SQLiteDatabase database = new DBHelper(activity).getWritableDatabase();
|
||||
ImportExportResult result;
|
||||
|
||||
if (doImport) {
|
||||
result = performImport(activity.getApplicationContext(), inputStream, database, password);
|
||||
} else {
|
||||
result = performExport(activity.getApplicationContext(), outputStream, database, password);
|
||||
}
|
||||
|
||||
database.close();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void onPostExecute(Object castResult) {
|
||||
listener.onTaskComplete((ImportExportResult) castResult, format);
|
||||
|
||||
progress.dismiss();
|
||||
Log.i(TAG, (doImport ? "Import" : "Export") + " Complete");
|
||||
}
|
||||
|
||||
protected void onCancelled() {
|
||||
progress.dismiss();
|
||||
Log.i(TAG, (doImport ? "Import" : "Export") + " Cancelled");
|
||||
}
|
||||
|
||||
protected void stop() {
|
||||
// Whelp
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImportExportResult call() {
|
||||
return doInBackground();
|
||||
}
|
||||
|
||||
interface TaskCompleteListener {
|
||||
void onTaskComplete(ImportExportResult result, DataFormat format);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,9 +26,13 @@ import androidx.appcompat.view.ActionMode;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.core.splashscreen.SplashScreen;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.work.Data;
|
||||
import androidx.work.WorkInfo;
|
||||
import androidx.work.WorkManager;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
@@ -36,11 +40,15 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import protect.card_locker.databinding.ContentMainBinding;
|
||||
import protect.card_locker.databinding.MainActivityBinding;
|
||||
import protect.card_locker.databinding.SortingOptionBinding;
|
||||
import protect.card_locker.importexport.DataFormat;
|
||||
import protect.card_locker.importexport.ImportExportWorker;
|
||||
import protect.card_locker.preferences.SettingsActivity;
|
||||
|
||||
public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener {
|
||||
@@ -71,6 +79,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
||||
|
||||
private ActivityResultLauncher<Intent> mBarcodeScannerLauncher;
|
||||
private ActivityResultLauncher<Intent> mSettingsLauncher;
|
||||
private ActivityResultLauncher<Intent> mImportExportLauncher;
|
||||
|
||||
private ActionMode.Callback mCurrentActionModeCallback = new ActionMode.Callback() {
|
||||
@Override
|
||||
@@ -304,6 +313,69 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
||||
}
|
||||
});
|
||||
|
||||
mImportExportLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
|
||||
// User didn't ask for import or export
|
||||
if (result.getResultCode() != RESULT_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Watch for active imports/exports
|
||||
new Thread(() -> {
|
||||
WorkManager workManager = WorkManager.getInstance(MainActivity.this);
|
||||
|
||||
Snackbar importRunning = Snackbar.make(binding.getRoot(), R.string.importing, Snackbar.LENGTH_INDEFINITE);
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
List<WorkInfo> activeImports = workManager.getWorkInfosForUniqueWork(ImportExportWorker.ACTION_IMPORT).get();
|
||||
|
||||
// We should only have one import running at a time, so it should be safe to always grab the latest
|
||||
WorkInfo activeImport = activeImports.get(activeImports.size() - 1);
|
||||
WorkInfo.State importState = activeImport.getState();
|
||||
|
||||
if (importState == WorkInfo.State.RUNNING || importState == WorkInfo.State.ENQUEUED || importState == WorkInfo.State.BLOCKED) {
|
||||
importRunning.show();
|
||||
} else if (importState == WorkInfo.State.SUCCEEDED) {
|
||||
importRunning.dismiss();
|
||||
runOnUiThread(() -> {
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.importSuccessful), Toast.LENGTH_LONG).show();
|
||||
updateLoyaltyCardList(true);
|
||||
});
|
||||
|
||||
break;
|
||||
} else {
|
||||
importRunning.dismiss();
|
||||
|
||||
Data outputData = activeImport.getOutputData();
|
||||
|
||||
// FIXME: This dialog will asynchronously be accepted or declined and we don't know the status of it so we can't show the import state
|
||||
// We want to get back into this function
|
||||
// A cheap fix would be to keep looping but if the user dismissed the dialog that could mean we're looping forever...
|
||||
if (Objects.equals(outputData.getString(ImportExportWorker.OUTPUT_ERROR_REASON), ImportExportWorker.ERROR_PASSWORD_REQUIRED)) {
|
||||
runOnUiThread(() -> ImportExportActivity.retryWithPassword(
|
||||
MainActivity.this,
|
||||
DataFormat.valueOf(outputData.getString(ImportExportWorker.INPUT_FORMAT)),
|
||||
Uri.parse(outputData.getString(ImportExportWorker.INPUT_URI))
|
||||
));
|
||||
} else {
|
||||
runOnUiThread(() -> {
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.importFailed), Toast.LENGTH_LONG).show();
|
||||
Toast.makeText(getApplicationContext(), activeImport.getOutputData().getString(ImportExportWorker.OUTPUT_ERROR_REASON), Toast.LENGTH_LONG).show();
|
||||
Toast.makeText(getApplicationContext(), activeImport.getOutputData().getString(ImportExportWorker.OUTPUT_ERROR_DETAILS), Toast.LENGTH_LONG).show();
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
});
|
||||
|
||||
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
@@ -641,7 +713,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
|
||||
|
||||
if (id == R.id.action_import_export) {
|
||||
Intent i = new Intent(getApplicationContext(), ImportExportActivity.class);
|
||||
startActivity(i);
|
||||
mImportExportLauncher.launch(i);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package protect.card_locker;
|
||||
|
||||
import static android.content.Context.NOTIFICATION_SERVICE;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class NotificationHelper {
|
||||
|
||||
// Do not change these IDs!
|
||||
public static final String CHANNEL_IMPORT = "import";
|
||||
|
||||
public static final String CHANNEL_EXPORT = "export";
|
||||
|
||||
public static final int IMPORT_ID = 100;
|
||||
public static final int IMPORT_PROGRESS_ID = 101;
|
||||
public static final int EXPORT_ID = 103;
|
||||
public static final int EXPORT_PROGRESS_ID = 104;
|
||||
|
||||
|
||||
public static Notification.Builder createNotificationBuilder(@NonNull Context context, @NonNull String channel, @NonNull int icon, @NonNull String title, @Nullable String message) {
|
||||
Notification.Builder notificationBuilder = new Notification.Builder(context)
|
||||
.setSmallIcon(icon)
|
||||
.setTicker(title)
|
||||
.setContentTitle(title);
|
||||
|
||||
if (message != null) {
|
||||
notificationBuilder.setContentText(message);
|
||||
}
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationChannel notificationChannel = new NotificationChannel(channel, getChannelName(channel), NotificationManager.IMPORTANCE_DEFAULT);
|
||||
notificationManager.createNotificationChannel(notificationChannel);
|
||||
|
||||
notificationBuilder.setChannelId(channel);
|
||||
}
|
||||
|
||||
return notificationBuilder;
|
||||
}
|
||||
|
||||
public static void sendNotification(@NonNull Context context, @NonNull int notificationId, @NonNull Notification notification) {
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
|
||||
|
||||
notificationManager.notify(notificationId, notification);
|
||||
}
|
||||
|
||||
private static String getChannelName(@NonNull String channel) {
|
||||
switch(channel) {
|
||||
case CHANNEL_IMPORT:
|
||||
return "Import";
|
||||
case CHANNEL_EXPORT:
|
||||
return "Export";
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown notification channel");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,16 @@ public class PermissionUtils {
|
||||
return ContextCompat.checkSelfPermission(activity, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if post notifications permission is needed
|
||||
*
|
||||
* @param activity
|
||||
* @return
|
||||
*/
|
||||
public static boolean needsPostNotificationsPermission(Activity activity) {
|
||||
return ContextCompat.checkSelfPermission(activity, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call onRequestPermissionsResult after storage read permission was granted.
|
||||
* Mocks a successful grant if a grant is not necessary.
|
||||
@@ -91,4 +101,37 @@ public class PermissionUtils {
|
||||
activity.onMockedRequestPermissionsResult(requestCode, permissions, mockedResults);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Call onRequestPermissionsResult after notification permission was granted.
|
||||
* Mocks a successful grant if a grant is not necessary.
|
||||
*
|
||||
* @param activity
|
||||
* @param requestCode
|
||||
*/
|
||||
public static void requestPostNotificationsPermission(CatimaAppCompatActivity activity, int requestCode) {
|
||||
int[] mockedResults = new int[]{ PackageManager.PERMISSION_GRANTED };
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.TIRAMISU) {
|
||||
String[] permissions = new String[0];
|
||||
activity.onMockedRequestPermissionsResult(requestCode, permissions, mockedResults);
|
||||
return;
|
||||
}
|
||||
|
||||
String[] permissions = new String[]{ Manifest.permission.POST_NOTIFICATIONS};
|
||||
|
||||
if (needsPostNotificationsPermission(activity)) {
|
||||
ActivityCompat.requestPermissions(activity, permissions, requestCode);
|
||||
} else {
|
||||
// FIXME: This points to onMockedRequestPermissionResult instead of to
|
||||
// onRequestPermissionResult because onRequestPermissionResult was only introduced in
|
||||
// Android 6.0 (SDK 23) and we and to support Android 5.0 (SDK 21) too.
|
||||
//
|
||||
// When minSdk becomes 23, this should point to onRequestPermissionResult directly and
|
||||
// the activity input variable should be changed from CatimaAppCompatActivity to
|
||||
// Activity.
|
||||
activity.onMockedRequestPermissionsResult(requestCode, permissions, mockedResults);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
package protect.card_locker.importexport;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.work.Data;
|
||||
import androidx.work.ForegroundInfo;
|
||||
import androidx.work.WorkManager;
|
||||
import androidx.work.Worker;
|
||||
import androidx.work.WorkerParameters;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import protect.card_locker.DBHelper;
|
||||
import protect.card_locker.NotificationHelper;
|
||||
import protect.card_locker.R;
|
||||
|
||||
public class ImportExportWorker extends Worker {
|
||||
private final String TAG = "Catima";
|
||||
|
||||
public static final String INPUT_URI = "uri";
|
||||
public static final String INPUT_ACTION = "action";
|
||||
public static final String INPUT_FORMAT = "format";
|
||||
public static final String INPUT_PASSWORD = "password";
|
||||
|
||||
public static final String ACTION_IMPORT = "import";
|
||||
public static final String ACTION_EXPORT = "export";
|
||||
|
||||
public static final String OUTPUT_ERROR_REASON = "errorReason";
|
||||
public static final String ERROR_GENERIC = "errorTypeGeneric";
|
||||
public static final String ERROR_PASSWORD_REQUIRED = "errorTypePasswordRequired";
|
||||
public static final String OUTPUT_ERROR_DETAILS = "errorDetails";
|
||||
|
||||
public ImportExportWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
|
||||
super(context, workerParams);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Result doWork() {
|
||||
Log.e("CATIMA", "Started import/export worker");
|
||||
|
||||
Context context = getApplicationContext();
|
||||
|
||||
Data inputData = getInputData();
|
||||
|
||||
String uriString = inputData.getString(INPUT_URI);
|
||||
String action = inputData.getString(INPUT_ACTION);
|
||||
String format = inputData.getString(INPUT_FORMAT);
|
||||
String password = inputData.getString(INPUT_PASSWORD);
|
||||
|
||||
if (action.equals(ACTION_IMPORT)) {
|
||||
Log.e("CATIMA", "Import requested");
|
||||
|
||||
setForegroundAsync(createForegroundInfo(NotificationHelper.CHANNEL_IMPORT, NotificationHelper.IMPORT_PROGRESS_ID, R.string.importing));
|
||||
|
||||
ImportExportResult result;
|
||||
|
||||
InputStream stream;
|
||||
try {
|
||||
stream = context.getContentResolver().openInputStream(Uri.parse(uriString));
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
final SQLiteDatabase database = new DBHelper(context).getWritableDatabase();
|
||||
|
||||
try {
|
||||
InputStreamReader writer = new InputStreamReader(stream, StandardCharsets.UTF_8);
|
||||
result = MultiFormatImporter.importData(context, database, stream, DataFormat.valueOf(format), password.toCharArray());
|
||||
writer.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Unable to import file", e);
|
||||
NotificationHelper.sendNotification(context, NotificationHelper.IMPORT_ID, NotificationHelper.createNotificationBuilder(context, NotificationHelper.CHANNEL_IMPORT, R.drawable.ic_import_export_white_24dp, context.getString(R.string.importFailedTitle), e.getLocalizedMessage()).build());
|
||||
|
||||
Data failureData = new Data.Builder()
|
||||
.putString(OUTPUT_ERROR_REASON, ERROR_GENERIC)
|
||||
.putString(OUTPUT_ERROR_DETAILS, e.getLocalizedMessage())
|
||||
.putString(INPUT_URI, uriString)
|
||||
.putString(INPUT_ACTION, action)
|
||||
.putString(INPUT_FORMAT, format)
|
||||
.putString(INPUT_PASSWORD, password)
|
||||
.build();
|
||||
|
||||
return Result.failure(failureData);
|
||||
}
|
||||
|
||||
Log.i(TAG, "Import result: " + result);
|
||||
|
||||
if (result.resultType() == ImportExportResultType.Success) {
|
||||
NotificationHelper.sendNotification(context, NotificationHelper.IMPORT_ID, NotificationHelper.createNotificationBuilder(context, NotificationHelper.CHANNEL_IMPORT, R.drawable.ic_import_export_white_24dp, context.getString(R.string.importSuccessfulTitle), context.getString(R.string.importSuccessful)).build());
|
||||
|
||||
return Result.success();
|
||||
} else if (result.resultType() == ImportExportResultType.BadPassword) {
|
||||
Log.e(TAG, "Needs password, unhandled for now");
|
||||
NotificationHelper.sendNotification(context, NotificationHelper.IMPORT_ID, NotificationHelper.createNotificationBuilder(context, NotificationHelper.CHANNEL_IMPORT, R.drawable.ic_import_export_white_24dp, context.getString(R.string.importing), context.getString(R.string.passwordRequired)).build());
|
||||
|
||||
Data failureData = new Data.Builder()
|
||||
.putString(OUTPUT_ERROR_REASON, ERROR_PASSWORD_REQUIRED)
|
||||
.putString(OUTPUT_ERROR_DETAILS, result.developerDetails())
|
||||
.putString(INPUT_URI, uriString)
|
||||
.putString(INPUT_ACTION, action)
|
||||
.putString(INPUT_FORMAT, format)
|
||||
.putString(INPUT_PASSWORD, password)
|
||||
.build();
|
||||
|
||||
return Result.failure(failureData);
|
||||
} else {
|
||||
NotificationHelper.sendNotification(context, NotificationHelper.IMPORT_ID, NotificationHelper.createNotificationBuilder(context, NotificationHelper.CHANNEL_IMPORT, R.drawable.ic_import_export_white_24dp, context.getString(R.string.importFailedTitle), context.getString(R.string.importFailed)).build());
|
||||
|
||||
Data failureData = new Data.Builder()
|
||||
.putString(OUTPUT_ERROR_REASON, ERROR_GENERIC)
|
||||
.putString(OUTPUT_ERROR_DETAILS, result.developerDetails())
|
||||
.putString(INPUT_URI, uriString)
|
||||
.putString(INPUT_ACTION, action)
|
||||
.putString(INPUT_FORMAT, format)
|
||||
.putString(INPUT_PASSWORD, password)
|
||||
.build();
|
||||
|
||||
return Result.failure(failureData);
|
||||
}
|
||||
} else {
|
||||
Log.e("CATIMA", "Export requested");
|
||||
|
||||
setForegroundAsync(createForegroundInfo(NotificationHelper.CHANNEL_EXPORT, NotificationHelper.EXPORT_PROGRESS_ID, R.string.exporting));
|
||||
|
||||
ImportExportResult result;
|
||||
|
||||
OutputStream stream;
|
||||
try {
|
||||
stream = context.getContentResolver().openOutputStream(Uri.parse(uriString));
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
final SQLiteDatabase database = new DBHelper(context).getReadableDatabase();
|
||||
|
||||
try {
|
||||
OutputStreamWriter writer = new OutputStreamWriter(stream, StandardCharsets.UTF_8);
|
||||
result = MultiFormatExporter.exportData(context, database, stream, DataFormat.valueOf(format), password.toCharArray());
|
||||
writer.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Unable to export file", e);
|
||||
NotificationHelper.sendNotification(context, NotificationHelper.EXPORT_ID, NotificationHelper.createNotificationBuilder(context, NotificationHelper.CHANNEL_EXPORT, R.drawable.ic_import_export_white_24dp, context.getString(R.string.exportFailedTitle), e.getLocalizedMessage()).build());
|
||||
|
||||
Data failureData = new Data.Builder()
|
||||
.putString(OUTPUT_ERROR_REASON, ERROR_GENERIC)
|
||||
.putString(OUTPUT_ERROR_DETAILS, e.getLocalizedMessage())
|
||||
.putString(INPUT_URI, uriString)
|
||||
.putString(INPUT_ACTION, action)
|
||||
.putString(INPUT_FORMAT, format)
|
||||
.putString(INPUT_PASSWORD, password)
|
||||
.build();
|
||||
|
||||
return Result.failure(failureData);
|
||||
}
|
||||
|
||||
Log.i(TAG, "Export result: " + result);
|
||||
|
||||
if (result.resultType() == ImportExportResultType.Success) {
|
||||
NotificationHelper.sendNotification(context, NotificationHelper.EXPORT_ID, NotificationHelper.createNotificationBuilder(context, NotificationHelper.CHANNEL_EXPORT, R.drawable.ic_import_export_white_24dp, context.getString(R.string.exportSuccessfulTitle), context.getString(R.string.exportSuccessful)).build());
|
||||
|
||||
return Result.success();
|
||||
} else {
|
||||
NotificationHelper.sendNotification(context, NotificationHelper.EXPORT_ID, NotificationHelper.createNotificationBuilder(context, NotificationHelper.CHANNEL_EXPORT, R.drawable.ic_import_export_white_24dp, context.getString(R.string.exportFailedTitle), context.getString(R.string.exportFailed)).build());
|
||||
|
||||
Data failureData = new Data.Builder()
|
||||
.putString(OUTPUT_ERROR_REASON, ERROR_GENERIC)
|
||||
.putString(OUTPUT_ERROR_DETAILS, result.developerDetails())
|
||||
.putString(INPUT_URI, uriString)
|
||||
.putString(INPUT_ACTION, action)
|
||||
.putString(INPUT_FORMAT, format)
|
||||
.putString(INPUT_PASSWORD, password)
|
||||
.build();
|
||||
|
||||
return Result.failure(failureData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private ForegroundInfo createForegroundInfo(@NonNull String channel, int notificationId, int title) {
|
||||
Context context = getApplicationContext();
|
||||
|
||||
String cancel = context.getString(R.string.cancel);
|
||||
// This PendingIntent can be used to cancel the worker
|
||||
PendingIntent intent = WorkManager.getInstance(context)
|
||||
.createCancelPendingIntent(getId());
|
||||
|
||||
Notification.Builder notificationBuilder = NotificationHelper.createNotificationBuilder(context, channel, R.drawable.ic_import_export_white_24dp, context.getString(title), null);
|
||||
|
||||
Notification notification = notificationBuilder
|
||||
.setOngoing(true)
|
||||
// Add the cancel action to the notification which can
|
||||
// be used to cancel the worker
|
||||
.addAction(android.R.drawable.ic_delete, cancel, intent)
|
||||
.build();
|
||||
|
||||
return new ForegroundInfo(notificationId, notification);
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ public class MultiFormatImporter {
|
||||
break;
|
||||
}
|
||||
|
||||
String error = null;
|
||||
String error;
|
||||
if (importer != null) {
|
||||
File inputFile;
|
||||
try {
|
||||
|
||||
@@ -346,4 +346,7 @@
|
||||
<string name="failedLaunchingFileManager">Could not find a supported file manager</string>
|
||||
<string name="multipleBarcodesFoundPleaseChooseOne">Which of the found barcodes do you want to use?</string>
|
||||
<string name="pageWithNumber">Page <xliff:g>%d</xliff:g></string>
|
||||
<string name="exportStartedCheckNotifications">Export started, check your notifications for the result</string>
|
||||
<string name="importStartedCheckNotifications">Import started, check your notifications for the result</string>
|
||||
<string name="postNotificationsPermissionRequired">Permission to show notifications needed for this action…</string>
|
||||
</resources>
|
||||
|
||||
@@ -574,72 +574,6 @@ public class ImportExportTest {
|
||||
}
|
||||
}
|
||||
|
||||
class TestTaskCompleteListener implements ImportExportTask.TaskCompleteListener {
|
||||
ImportExportResult result;
|
||||
|
||||
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat) {
|
||||
this.result = result;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@LooperMode(LooperMode.Mode.PAUSED)
|
||||
public void useImportExportTask() throws FileNotFoundException {
|
||||
final int NUM_CARDS = 10;
|
||||
|
||||
final File sdcardDir = Environment.getExternalStorageDirectory();
|
||||
final File exportFile = new File(sdcardDir, "Catima.csv");
|
||||
|
||||
TestHelpers.addLoyaltyCards(mDatabase, NUM_CARDS);
|
||||
|
||||
TestTaskCompleteListener listener = new TestTaskCompleteListener();
|
||||
|
||||
// Export to the file
|
||||
final String password = "123456789";
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(exportFile);
|
||||
ImportExportTask task = new ImportExportTask(activity, DataFormat.Catima, fileOutputStream, password.toCharArray(), listener);
|
||||
TaskHandler mTasks = new TaskHandler();
|
||||
mTasks.executeTask(TaskHandler.TYPE.EXPORT, task);
|
||||
|
||||
// Actually run the task to completion
|
||||
mTasks.flushTaskList(TaskHandler.TYPE.EXPORT, false, false, true);
|
||||
shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(5000));
|
||||
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
|
||||
|
||||
|
||||
// Check that the listener was executed
|
||||
assertNotNull(listener.result);
|
||||
assertEquals(ImportExportResultType.Success, listener.result.resultType());
|
||||
|
||||
TestHelpers.getEmptyDb(activity);
|
||||
|
||||
// Import everything back from the default location
|
||||
|
||||
listener = new TestTaskCompleteListener();
|
||||
|
||||
FileInputStream fileStream = new FileInputStream(exportFile);
|
||||
|
||||
task = new ImportExportTask(activity, DataFormat.Catima, fileStream, password.toCharArray(), listener);
|
||||
mTasks.executeTask(TaskHandler.TYPE.IMPORT, task);
|
||||
|
||||
// Actually run the task to completion
|
||||
// I am CONVINCED there must be a better way than to wait on this Queue with a flush.
|
||||
mTasks.flushTaskList(TaskHandler.TYPE.IMPORT, false, false, true);
|
||||
shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(5000));
|
||||
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
|
||||
|
||||
// Check that the listener was executed
|
||||
assertNotNull(listener.result);
|
||||
assertEquals(ImportExportResultType.Success, listener.result.resultType());
|
||||
|
||||
assertEquals(NUM_CARDS, DBHelper.getLoyaltyCardCount(mDatabase));
|
||||
|
||||
checkLoyaltyCards();
|
||||
|
||||
// Clear the database for the next format under test
|
||||
TestHelpers.getEmptyDb(activity);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void importWithoutColorsV1() {
|
||||
InputStream inputStream = getClass().getResourceAsStream("catima_v1_no_colors.csv");
|
||||
|
||||
Reference in New Issue
Block a user