Merge pull request #245 from TheLastProject/feature/StocardImport

Stocard import
This commit is contained in:
Sylvia van Os
2021-07-08 19:41:22 +02:00
committed by GitHub
29 changed files with 557 additions and 181 deletions

View File

@@ -6,6 +6,8 @@ Changes:
- Add UPC-E support
- Support adding a front and back photo to each card
- Support importing password-protected zip files
- Support importing from Stocard (Beta)
- Fix useless whitespace in notes from Fidme import
## v1.14.1 (2021-06-14)

View File

@@ -84,6 +84,7 @@ dependencies {
implementation 'com.jaredrummler:colorpicker:1.1.0'
implementation 'com.google.guava:guava:30.1.1-jre'
implementation 'com.github.invissvenska:NumberPickerPreference:1.0.2'
implementation 'net.lingala.zip4j:zip4j:2.8.0'
// SpotBugs
implementation 'io.wcm.tooling.spotbugs:io.wcm.tooling.spotbugs.annotations:1.0.0'

View File

@@ -6,7 +6,6 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap;
import com.google.zxing.BarcodeFormat;
@@ -275,7 +274,7 @@ public class DBHelper extends SQLiteOpenHelper
return newId;
}
public boolean insertLoyaltyCard(final SQLiteDatabase db, final String store,
public long insertLoyaltyCard(final SQLiteDatabase db, final String store,
final String note, final Date expiry, final BigDecimal balance,
final Currency balanceType, final String cardId,
final String barcodeId, final BarcodeFormat barcodeType,
@@ -293,10 +292,10 @@ public class DBHelper extends SQLiteOpenHelper
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
contentValues.put(LoyaltyCardDbIds.STAR_STATUS,starStatus);
final long newId = db.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
return (newId != -1);
return newId;
}
public boolean insertLoyaltyCard(final SQLiteDatabase db, final int id, final String store,
public long insertLoyaltyCard(final SQLiteDatabase db, final int id, final String store,
final String note, final Date expiry, final BigDecimal balance,
final Currency balanceType, final String cardId,
final String barcodeId, final BarcodeFormat barcodeType,
@@ -315,7 +314,7 @@ public class DBHelper extends SQLiteOpenHelper
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
contentValues.put(LoyaltyCardDbIds.STAR_STATUS,starStatus);
final long newId = db.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
return (newId != -1);
return newId;
}
public boolean updateLoyaltyCard(final int id, final String store, final String note,

View File

@@ -8,10 +8,12 @@ import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.InputType;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import java.io.File;
@@ -20,6 +22,8 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
@@ -27,6 +31,8 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import protect.card_locker.importexport.DataFormat;
import protect.card_locker.importexport.ImportExportResult;
public class ImportExportActivity extends AppCompatActivity
{
@@ -115,9 +121,22 @@ public class ImportExportActivity extends AppCompatActivity
}
private void chooseImportType(Intent baseIntent) {
List<CharSequence> betaImportOptions = new ArrayList<>();
betaImportOptions.add("Fidme");
betaImportOptions.add("Stocard");
List<CharSequence> importOptions = new ArrayList<>();
for (String importOption : getResources().getStringArray(R.array.import_types_array)) {
if (betaImportOptions.contains(importOption)) {
importOption = importOption + " (BETA)";
}
importOptions.add(importOption);
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.chooseImportType)
.setItems(R.array.import_types_array, (dialog, which) -> {
.setItems(importOptions.toArray(new CharSequence[importOptions.size()]), (dialog, which) -> {
switch (which) {
// Catima
case 0:
@@ -137,8 +156,14 @@ public class ImportExportActivity extends AppCompatActivity
importAlertMessage = getString(R.string.importLoyaltyCardKeychainMessage);
importDataFormat = DataFormat.Catima;
break;
// Voucher Vault
// Stocard
case 3:
importAlertTitle = getString(R.string.importStocard);
importAlertMessage = getString(R.string.importStocardMessage);
importDataFormat = DataFormat.Stocard;
break;
// Voucher Vault
case 4:
importAlertTitle = getString(R.string.importVoucherVault);
importAlertMessage = getString(R.string.importVoucherVaultMessage);
importDataFormat = DataFormat.VoucherVault;
@@ -162,19 +187,19 @@ public class ImportExportActivity extends AppCompatActivity
builder.show();
}
private void startImport(final InputStream target, final Uri targetUri, final DataFormat dataFormat)
private void startImport(final InputStream target, final Uri targetUri, final DataFormat dataFormat, final char[] password)
{
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener()
{
@Override
public void onTaskComplete(boolean success)
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat)
{
onImportComplete(success, targetUri);
onImportComplete(result, targetUri, dataFormat);
}
};
importExporter = new ImportExportTask(ImportExportActivity.this,
dataFormat, target, listener);
dataFormat, target, password, listener);
importExporter.execute();
}
@@ -183,9 +208,9 @@ public class ImportExportActivity extends AppCompatActivity
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener()
{
@Override
public void onTaskComplete(boolean success)
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat)
{
onExportComplete(success, targetUri);
onExportComplete(result, targetUri);
}
};
@@ -210,7 +235,7 @@ public class ImportExportActivity extends AppCompatActivity
}
}
if(success == false)
if(!success)
{
// External storage permission rejected, inform user that
// import/export is prevented
@@ -245,20 +270,43 @@ public class ImportExportActivity extends AppCompatActivity
return super.onOptionsItemSelected(item);
}
private void onImportComplete(boolean success, Uri path)
{
private void retryWithPassword(DataFormat dataFormat, Uri uri) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.passwordRequired);
final EditText input = new EditText(this);
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
builder.setView(input);
builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> {
activityResultParser(IMPORT, RESULT_OK, uri, input.getText().toString().toCharArray());
});
builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.cancel());
builder.show();
}
private void onImportComplete(ImportExportResult result, Uri path, DataFormat dataFormat) {
if (result == ImportExportResult.BadPassword) {
retryWithPassword(dataFormat, path);
return;
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
if(success)
int messageId;
if (result == ImportExportResult.Success)
{
builder.setTitle(R.string.importSuccessfulTitle);
messageId = R.string.importSuccessful;
}
else
{
builder.setTitle(R.string.importFailedTitle);
messageId = R.string.importFailed;
}
int messageId = success ? R.string.importSuccessful : R.string.importFailed;
final String message = getResources().getString(messageId);
builder.setMessage(message);
@@ -274,53 +322,44 @@ public class ImportExportActivity extends AppCompatActivity
builder.create().show();
}
private void onExportComplete(boolean success, final Uri path)
private void onExportComplete(ImportExportResult result, final Uri path)
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
if(success)
int messageId;
if(result == ImportExportResult.Success)
{
builder.setTitle(R.string.exportSuccessfulTitle);
messageId = R.string.exportSuccessful;
}
else
{
builder.setTitle(R.string.exportFailedTitle);
messageId = R.string.exportFailed;
}
int messageId = success ? R.string.exportSuccessful : R.string.exportFailed;
final String message = getResources().getString(messageId);
builder.setMessage(message);
builder.setNeutralButton(R.string.ok, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
}
});
builder.setNeutralButton(R.string.ok, (dialog, which) -> dialog.dismiss());
if(success)
if(result == ImportExportResult.Success)
{
final CharSequence sendLabel = ImportExportActivity.this.getResources().getText(R.string.sendLabel);
builder.setPositiveButton(sendLabel, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_STREAM, path);
sendIntent.setType("text/csv");
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);
// 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));
ImportExportActivity.this.startActivity(Intent.createChooser(sendIntent,
sendLabel));
dialog.dismiss();
}
dialog.dismiss();
});
}
@@ -340,18 +379,13 @@ public class ImportExportActivity extends AppCompatActivity
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
private void activityResultParser(int requestCode, int resultCode, Uri uri, char[] password) {
if (resultCode != RESULT_OK)
{
Log.w(TAG, "Failed onActivityResult(), result=" + resultCode);
return;
}
Uri uri = data.getData();
if(uri == null)
{
Log.e(TAG, "Activity returned a NULL URI");
@@ -389,7 +423,7 @@ public class ImportExportActivity extends AppCompatActivity
Log.e(TAG, "Starting file import with: " + uri.toString());
startImport(reader, uri, importDataFormat);
startImport(reader, uri, importDataFormat, password);
}
}
catch(FileNotFoundException e)
@@ -397,12 +431,26 @@ public class ImportExportActivity extends AppCompatActivity
Log.e(TAG, "Failed to import/export file: " + uri.toString(), e);
if (requestCode == CHOOSE_EXPORT_LOCATION)
{
onExportComplete(false, uri);
onExportComplete(ImportExportResult.GenericFailure, uri);
}
else
{
onImportComplete(false, uri);
onImportComplete(ImportExportResult.GenericFailure, uri, importDataFormat);
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
if(data == null)
{
Log.e(TAG, "Activity returned NULL data");
return;
}
activityResultParser(requestCode, resultCode, data.getData(), null);
}
}

View File

@@ -13,10 +13,12 @@ import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import protect.card_locker.importexport.DataFormat;
import protect.card_locker.importexport.ImportExportResult;
import protect.card_locker.importexport.MultiFormatExporter;
import protect.card_locker.importexport.MultiFormatImporter;
class ImportExportTask extends AsyncTask<Void, Void, Boolean>
class ImportExportTask extends AsyncTask<Void, Void, ImportExportResult>
{
private static final String TAG = "Catima";
@@ -25,6 +27,7 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
private DataFormat format;
private OutputStream outputStream;
private InputStream inputStream;
private char[] password;
private TaskCompleteListener listener;
private ProgressDialog progress;
@@ -46,7 +49,7 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
/**
* Constructor which will setup a task for importing from the given InputStream.
*/
ImportExportTask(Activity activity, DataFormat format, InputStream input,
ImportExportTask(Activity activity, DataFormat format, InputStream input, char[] password,
TaskCompleteListener listener)
{
super();
@@ -54,24 +57,22 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
this.doImport = true;
this.format = format;
this.inputStream = input;
this.password = password;
this.listener = listener;
}
private boolean performImport(Context context, InputStream stream, DBHelper db)
private ImportExportResult performImport(Context context, InputStream stream, DBHelper db, char[] password)
{
boolean result = false;
ImportExportResult importResult = MultiFormatImporter.importData(context, db, stream, format, password);
Log.i(TAG, "Import result: " + importResult.name());
result = MultiFormatImporter.importData(context, db, stream, format);
Log.i(TAG, "Import result: " + result);
return result;
return importResult;
}
private boolean performExport(Context context, OutputStream stream, DBHelper db)
private ImportExportResult performExport(Context context, OutputStream stream, DBHelper db)
{
boolean result = false;
ImportExportResult result = ImportExportResult.GenericFailure;
try
{
@@ -106,14 +107,14 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
progress.show();
}
protected Boolean doInBackground(Void... nothing)
protected ImportExportResult doInBackground(Void... nothing)
{
final DBHelper db = new DBHelper(activity);
boolean result;
ImportExportResult result;
if(doImport)
{
result = performImport(activity.getApplicationContext(), inputStream, db);
result = performImport(activity.getApplicationContext(), inputStream, db, password);
}
else
{
@@ -123,9 +124,9 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
return result;
}
protected void onPostExecute(Boolean result)
protected void onPostExecute(ImportExportResult result)
{
listener.onTaskComplete(result);
listener.onTaskComplete(result, format);
progress.dismiss();
Log.i(TAG, (doImport ? "Import" : "Export") + " Complete");
@@ -138,7 +139,7 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
}
interface TaskCompleteListener
{
void onTaskComplete(boolean success);
void onTaskComplete(ImportExportResult result, DataFormat format);
}
}

View File

@@ -2,7 +2,6 @@ package protect.card_locker;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import com.google.zxing.BarcodeFormat;

View File

@@ -1,10 +1,6 @@
package protect.card_locker;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.sql.Blob;
import com.google.zxing.BarcodeFormat;

View File

@@ -104,9 +104,6 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
ImageView cardImageFront;
ImageView cardImageBack;
Bitmap frontImageBitmap;
Bitmap backImageBitmap;
Button enterButton;
int loyaltyCardId;

View File

@@ -89,6 +89,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
boolean starred;
boolean backgroundNeedsDarkIcons;
FullscreenType fullscreenType = FullscreenType.NONE;
boolean isBarcodeSupported = true;
enum FullscreenType {
NONE,
@@ -406,7 +407,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
}
expiryView.setTag(loyaltyCard.expiry);
if (fullscreenType != FullscreenType.NONE) {
if (fullscreenType == FullscreenType.NONE) {
makeBottomSheetVisibleIfUseful();
}
@@ -459,8 +460,6 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
// Set shadow colour of store text so even same color on same color would be readable
storeName.setShadowLayer(1, 1, 1, backgroundNeedsDarkIcons ? Color.BLACK : Color.WHITE);
Boolean isBarcodeSupported = true;
if (format != null && !BarcodeSelectorActivity.SUPPORTED_BARCODE_TYPES.contains(format.name())) {
isBarcodeSupported = false;
@@ -682,14 +681,15 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
private void setFullscreen(FullscreenType fullscreenType)
{
ActionBar actionBar = getSupportActionBar();
if (fullscreenType != FullscreenType.NONE)
{
Log.d(TAG, "Move into of fullscreen");
if (fullscreenType != FullscreenType.NONE) {
Log.d(TAG, "Move into fullscreen");
if (fullscreenType == FullscreenType.IMAGE_FRONT) {
barcodeImage.setImageBitmap(frontImageBitmap);
barcodeImage.setVisibility(View.VISIBLE);
} else if (fullscreenType == FullscreenType.IMAGE_BACK) {
barcodeImage.setImageBitmap(backImageBitmap);
barcodeImage.setVisibility(View.VISIBLE);
} else {
// Prepare redraw after size change
redrawBarcodeAfterResize();
@@ -737,10 +737,16 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
barcodeScaler.setProgress(100);
// Prepare redraw after size change
redrawBarcodeAfterResize();
if (format != null && isBarcodeSupported) {
redrawBarcodeAfterResize();
} else {
barcodeImage.setVisibility(View.GONE);
}
// Show maximize and hide minimize button and scaler
maximizeButton.setVisibility(View.VISIBLE);
if (format != null && isBarcodeSupported) {
maximizeButton.setVisibility(View.VISIBLE);
}
minimizeButton.setVisibility(View.GONE);
barcodeScaler.setVisibility(View.GONE);

View File

@@ -32,6 +32,7 @@ import java.util.Calendar;
import java.util.Currency;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import androidx.core.graphics.ColorUtils;
@@ -345,4 +346,12 @@ public class Utils {
return BitmapFactory.decodeStream(in);
}
static public Object hashmapGetOrDefault(HashMap hashMap, String key, Object defaultValue) {
Object value = hashMap.get(key);
if (value == null) {
return defaultValue;
}
return value;
}
}

View File

@@ -2,9 +2,6 @@ package protect.card_locker.importexport;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Base64;
import com.google.zxing.BarcodeFormat;
@@ -37,7 +34,7 @@ import protect.card_locker.Utils;
*/
public class CsvImporter implements Importer
{
public void importData(Context context, DBHelper db, InputStream input) throws IOException, FormatException, InterruptedException
public void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, InterruptedException
{
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));

View File

@@ -1,9 +1,10 @@
package protect.card_locker;
package protect.card_locker.importexport;
public enum DataFormat
{
Catima,
Fidme,
Stocard,
VoucherVault
;
}

View File

@@ -5,6 +5,9 @@ import android.database.sqlite.SQLiteDatabase;
import com.google.zxing.BarcodeFormat;
import net.lingala.zip4j.io.inputstream.ZipInputStream;
import net.lingala.zip4j.model.LocalFileHeader;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
@@ -16,8 +19,6 @@ import java.io.StringReader;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import protect.card_locker.DBHelper;
import protect.card_locker.FormatException;
@@ -31,18 +32,18 @@ import protect.card_locker.FormatException;
*/
public class FidmeImporter implements Importer
{
public void importData(Context context, DBHelper db, InputStream input) throws IOException, FormatException, JSONException, ParseException {
public void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, JSONException, ParseException {
// We actually retrieve a .zip file
ZipInputStream zipInputStream = new ZipInputStream(input);
ZipInputStream zipInputStream = new ZipInputStream(input, password);
StringBuilder loyaltyCards = new StringBuilder();
byte[] buffer = new byte[1024];
int read = 0;
ZipEntry zipEntry;
LocalFileHeader localFileHeader;
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
if (zipEntry.getName().equals("loyalty_programs.csv")) {
while ((localFileHeader = zipInputStream.getNextEntry()) != null) {
if (localFileHeader.getFileName().equals("loyalty_programs.csv")) {
while ((read = zipInputStream.read(buffer, 0, 1024)) >= 0) {
loyaltyCards.append(new String(buffer, 0, read, StandardCharsets.UTF_8));
}

View File

@@ -0,0 +1,9 @@
package protect.card_locker.importexport;
public enum ImportExportResult
{
Success,
GenericFailure,
BadPassword
;
}

View File

@@ -23,5 +23,5 @@ public interface Importer
* @throws IOException
* @throws FormatException
*/
void importData(Context context, DBHelper db, InputStream input) throws IOException, FormatException, InterruptedException, JSONException, ParseException;
void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, InterruptedException, JSONException, ParseException;
}

View File

@@ -7,7 +7,6 @@ import java.io.IOException;
import java.io.OutputStreamWriter;
import protect.card_locker.DBHelper;
import protect.card_locker.DataFormat;
public class MultiFormatExporter
{
@@ -19,11 +18,11 @@ public class MultiFormatExporter
*
* The output stream is closed on success.
*
* @return true if the database was successfully exported,
* false otherwise. If false, partial data may have been
* @return ImportExportResult.Success if the database was successfully exported,
* another ImportExportResult otherwise. If not Success, partial data may have been
* written to the output stream, and it should be discarded.
*/
public static boolean exportData(Context context, DBHelper db, OutputStreamWriter output, DataFormat format)
public static ImportExportResult exportData(Context context, DBHelper db, OutputStreamWriter output, DataFormat format)
{
Exporter exporter = null;
@@ -42,7 +41,7 @@ public class MultiFormatExporter
try
{
exporter.exportData(context, db, output);
return true;
return ImportExportResult.Success;
}
catch(IOException e)
{
@@ -53,12 +52,12 @@ public class MultiFormatExporter
Log.e(TAG, "Failed to export data", e);
}
return false;
return ImportExportResult.GenericFailure;
}
else
{
Log.e(TAG, "Unsupported data format exported: " + format.name());
return false;
return ImportExportResult.GenericFailure;
}
}
}

View File

@@ -3,6 +3,8 @@ package protect.card_locker.importexport;
import android.content.Context;
import android.util.Log;
import net.lingala.zip4j.exception.ZipException;
import org.json.JSONException;
import java.io.IOException;
@@ -10,7 +12,6 @@ import java.io.InputStream;
import java.text.ParseException;
import protect.card_locker.DBHelper;
import protect.card_locker.DataFormat;
import protect.card_locker.FormatException;
public class MultiFormatImporter
@@ -24,11 +25,11 @@ public class MultiFormatImporter
* The input stream is not closed, and doing so is the
* responsibility of the caller.
*
* @return true if the database was successfully imported,
* false otherwise. If false, no data was written to
* @return ImportExportResult.Success if the database was successfully imported,
* or another result otherwise. If no Success, no data was written to
* the database.
*/
public static boolean importData(Context context, DBHelper db, InputStream input, DataFormat format)
public static ImportExportResult importData(Context context, DBHelper db, InputStream input, DataFormat format, char[] password)
{
Importer importer = null;
@@ -40,6 +41,9 @@ public class MultiFormatImporter
case Fidme:
importer = new FidmeImporter();
break;
case Stocard:
importer = new StocardImporter();
break;
case VoucherVault:
importer = new VoucherVaultImporter();
break;
@@ -49,8 +53,12 @@ public class MultiFormatImporter
{
try
{
importer.importData(context, db, input);
return true;
importer.importData(context, db, input, password);
return ImportExportResult.Success;
}
catch(ZipException e)
{
return ImportExportResult.BadPassword;
}
catch(IOException | FormatException | InterruptedException | JSONException | ParseException e)
{
@@ -62,6 +70,7 @@ public class MultiFormatImporter
{
Log.e(TAG, "Unsupported data format imported: " + format.name());
}
return false;
return ImportExportResult.GenericFailure;
}
}

View File

@@ -0,0 +1,249 @@
package protect.card_locker.importexport;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import com.google.zxing.BarcodeFormat;
import net.lingala.zip4j.io.inputstream.ZipInputStream;
import net.lingala.zip4j.model.LocalFileHeader;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import protect.card_locker.DBHelper;
import protect.card_locker.FormatException;
import protect.card_locker.Utils;
/**
* Class for importing a database from CSV (Comma Separate Values)
* formatted data.
*
* The database's loyalty cards are expected to appear in the CSV data.
* A header is expected for the each table showing the names of the columns.
*/
public class StocardImporter implements Importer
{
public void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, JSONException, ParseException {
HashMap<String, HashMap<String, Object>> loyaltyCardHashMap = new HashMap<>();
HashMap<String, String> providers = new HashMap<>();
ZipInputStream zipInputStream = new ZipInputStream(input, password);
String[] providersFileName = null;
String[] cardBaseName = null;
String cardName = "";
LocalFileHeader localFileHeader;
while ((localFileHeader = zipInputStream.getNextEntry()) != null) {
String fileName = localFileHeader.getFileName();
String[] nameParts = fileName.split("/");
if (providersFileName == null) {
providersFileName = new String[] {
nameParts[0],
"sync",
"data",
"users",
nameParts[0],
"analytics-properties.json"
};
cardBaseName = new String[] {
nameParts[0],
"sync",
"data",
"users",
nameParts[0],
"loyalty-cards"
};
}
if (startsWith(nameParts, providersFileName, 0) && !localFileHeader.isDirectory()) {
providers = parseProviders(zipInputStream);
} else if (startsWith(nameParts, cardBaseName, 1)) {
// Extract cardName
cardName = nameParts[cardBaseName.length].split("\\.", 2)[0];
// This is the card itself
if (nameParts.length == cardBaseName.length + 1) {
// Ignore the .txt file
if (fileName.endsWith(".json")) {
JSONObject jsonObject = readJSON(zipInputStream);
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
loyaltyCardHashMap,
cardName,
"cardId",
jsonObject.getString("input_id")
);
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
loyaltyCardHashMap,
cardName,
"_providerId",
jsonObject
.getJSONObject("input_provider_reference")
.getString("identifier")
.substring("/loyalty-card-providers/".length())
);
try {
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
loyaltyCardHashMap,
cardName,
"barcodeType",
jsonObject.getString("input_barcode_format")
);
} catch (JSONException ignored) {}
}
} else if (fileName.endsWith("notes/default.json")) {
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
loyaltyCardHashMap,
cardName,
"note",
readJSON(zipInputStream)
.getString("content")
);
} else if (fileName.endsWith("/images/front.png")) {
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
loyaltyCardHashMap,
cardName,
"frontImage",
readImage(zipInputStream)
);
} else if (fileName.endsWith("/images/back.png")) {
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
loyaltyCardHashMap,
cardName,
"backImage",
readImage(zipInputStream)
);
}
}
}
if (loyaltyCardHashMap.keySet().size() == 0) {
throw new FormatException("Couldn't find any loyalty cards in this Stocard export.");
}
SQLiteDatabase database = db.getWritableDatabase();
database.beginTransaction();
for (HashMap<String, Object> loyaltyCardData : loyaltyCardHashMap.values()) {
String store = providers.get(loyaltyCardData.get("_providerId").toString());
String note = (String) Utils.hashmapGetOrDefault(loyaltyCardData, "note", "");
String cardId = (String) loyaltyCardData.get("cardId");
String barcodeTypeString = (String) Utils.hashmapGetOrDefault(loyaltyCardData, "barcodeType", null);
BarcodeFormat barcodeType = null;
if (barcodeTypeString != null) {
if (barcodeTypeString.equals("RSS_DATABAR_EXPANDED")) {
barcodeType = BarcodeFormat.RSS_EXPANDED;
} else {
barcodeType = BarcodeFormat.valueOf(barcodeTypeString);
}
}
long loyaltyCardInternalId = db.insertLoyaltyCard(database, store, note, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, null, 0);
if (loyaltyCardData.containsKey("frontImage")) {
Utils.saveCardImage(context, (Bitmap) loyaltyCardData.get("frontImage"), (int) loyaltyCardInternalId, true);
}
if (loyaltyCardData.containsKey("backImage")) {
Utils.saveCardImage(context, (Bitmap) loyaltyCardData.get("backImage"), (int) loyaltyCardInternalId, false);
}
}
database.setTransactionSuccessful();
database.endTransaction();
database.close();
zipInputStream.close();
}
private boolean startsWith(String[] full, String[] start, int minExtraLength) {
if (full.length - minExtraLength < start.length) {
return false;
}
for (int i = 0; i < start.length; i++) {
if (!start[i].contentEquals(full[i])) {
return false;
}
}
return true;
}
private String read(ZipInputStream zipInputStream) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
Reader reader = new BufferedReader(new InputStreamReader(zipInputStream, Charset.forName(StandardCharsets.UTF_8.name())));
int c;
while ((c = reader.read()) != -1) {
stringBuilder.append((char) c);
}
return stringBuilder.toString();
}
private Bitmap readImage(ZipInputStream zipInputStream) {
return BitmapFactory.decodeStream(zipInputStream);
}
private JSONObject readJSON(ZipInputStream zipInputStream) throws IOException, JSONException {
return new JSONObject(read(zipInputStream));
}
private HashMap<String, HashMap<String, Object>> appendToLoyaltyCardHashMap(HashMap<String, HashMap<String, Object>> loyaltyCardHashMap, String cardID, String key, Object value) {
HashMap<String, Object> loyaltyCardData = loyaltyCardHashMap.get(cardID);
if (loyaltyCardData == null) {
loyaltyCardData = new HashMap<>();
}
loyaltyCardData.put(key, value);
loyaltyCardHashMap.put(cardID, loyaltyCardData);
return loyaltyCardHashMap;
}
private HashMap<String, String> parseProviders(ZipInputStream zipInputStream) throws IOException, JSONException {
// FIXME: This is probably completely wrong, but it works for the one and only test file I have
JSONObject jsonObject = readJSON(zipInputStream);
JSONArray providerIdList = jsonObject.getJSONArray("provider_id_list");
JSONArray providerList = jsonObject.getJSONArray("provider_list");
// Resort, put IDs with - in them after IDs without any -
List<String> providerIds = new ArrayList<>();
List<String> customProviderIds = new ArrayList<>();
for (int i = 0; i < providerIdList.length(); i++) {
String providerId = providerIdList.get(i).toString();
if (providerId.contains("-")) {
customProviderIds.add(providerId);
} else {
providerIds.add(providerId);
}
}
providerIds.addAll(customProviderIds);
HashMap<String, String> providers = new HashMap<>();
for (int i = 0; i < jsonObject.getInt("number_of_cards"); i++) {
providers.put(providerIds.get(i), providerList.get(i).toString());
}
return providers;
}
}

View File

@@ -35,7 +35,7 @@ import protect.card_locker.FormatException;
*/
public class VoucherVaultImporter implements Importer
{
public void importData(Context context, DBHelper db, InputStream input) throws IOException, FormatException, JSONException, ParseException {
public void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, JSONException, ParseException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();

View File

@@ -4,6 +4,7 @@
<item>Catima</item>
<item>Fidme</item>
<item>@string/app_loyalty_card_keychain</item>
<item>Stocard</item>
<item>Voucher Vault</item>
</string-array>
</resources>

View File

@@ -168,6 +168,8 @@
<string name="importFidmeMessage">Find a file likely named <i>fidme-export-request-xxxxxx.zip</i> to import, and then select the barcode types manually afterwards.\nOr create it from your FidMe profile by choosing "Data Protection" and then pressing "Extract my data" first.</string>
<string name="importLoyaltyCardKeychain">Import from Loyalty Card Keychain</string>
<string name="importLoyaltyCardKeychainMessage">Find a file most likely named <i>LoyaltyCardKeychain.csv</i> to import.\nOr create it from the Import/Export menu in Loyalty Card Keychain by pressing "Export" first.</string>
<string name="importStocard">Import from Stocard</string>
<string name="importStocardMessage">Find your Stocard .zip file to import, and then select the barcode types manually afterwards.\nOr get it by emailing support@stocardapp.com and asking for an export of your data first.</string>
<string name="importVoucherVault">Import from Voucher Vault</string>
<string name="importVoucherVaultMessage">Find a file likely named <i>vouchervault.json</i> to import.\nOr create it by pressing "Export" in Voucher Vault first.</string>
<string name="barcodeId">Barcode value</string>
@@ -189,4 +191,5 @@
<string name="updateBarcodeQuestionText">You changed the card ID. Do you want to also update the barcode to use the same value?</string>
<string name="yes">Yes</string>
<string name="no">No</string>
<string name="passwordRequired">Please enter the password</string>
</resources>

View File

@@ -1,17 +1,13 @@
package protect.card_locker;
import android.app.Activity;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Environment;
import android.util.DisplayMetrics;
import android.util.Log;
import com.google.zxing.BarcodeFormat;
@@ -24,7 +20,6 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.LooperMode;
import org.robolectric.shadows.ShadowLog;
import org.robolectric.util.Util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -45,9 +40,9 @@ import java.util.Currency;
import java.util.Date;
import java.util.List;
import javax.annotation.Resource;
import androidx.core.content.res.ResourcesCompat;
import protect.card_locker.importexport.DataFormat;
import protect.card_locker.importexport.ImportExportResult;
import protect.card_locker.importexport.MultiFormatExporter;
import protect.card_locker.importexport.MultiFormatImporter;
@@ -338,8 +333,8 @@ public class ImportExportTest
OutputStreamWriter outStream = new OutputStreamWriter(outData);
// Export data to CSV format
boolean result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima);
assertTrue(result);
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima);
assertEquals(ImportExportResult.Success, result);
outStream.close();
TestHelpers.getEmptyDb(activity);
@@ -347,8 +342,8 @@ public class ImportExportTest
ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray());
// Import the CSV data
result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, DataFormat.Catima);
assertTrue(result);
result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, DataFormat.Catima, null);
assertEquals(ImportExportResult.Success, result);
assertEquals(NUM_CARDS, db.getLoyaltyCardCount());
@@ -369,8 +364,8 @@ public class ImportExportTest
OutputStreamWriter outStream = new OutputStreamWriter(outData);
// Export data to CSV format
boolean result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima);
assertTrue(result);
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima);
assertEquals(ImportExportResult.Success, result);
outStream.close();
TestHelpers.getEmptyDb(activity);
@@ -378,8 +373,8 @@ public class ImportExportTest
ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray());
// Import the CSV data
result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, DataFormat.Catima);
assertTrue(result);
result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, DataFormat.Catima, null);
assertEquals(ImportExportResult.Success, result);
assertEquals(NUM_CARDS, db.getLoyaltyCardCount());
@@ -442,8 +437,8 @@ public class ImportExportTest
OutputStreamWriter outStream = new OutputStreamWriter(outData);
// Export data to CSV format
boolean result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima);
assertTrue(result);
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima);
assertEquals(ImportExportResult.Success, result);
outStream.close();
TestHelpers.getEmptyDb(activity);
@@ -451,8 +446,8 @@ public class ImportExportTest
ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray());
// Import the CSV data
result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, DataFormat.Catima);
assertTrue(result);
result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, DataFormat.Catima, null);
assertEquals(ImportExportResult.Success, result);
assertEquals(NUM_CARDS, db.getLoyaltyCardCount());
assertEquals(NUM_GROUPS, db.getGroupCount());
@@ -486,15 +481,15 @@ public class ImportExportTest
OutputStreamWriter outStream = new OutputStreamWriter(outData);
// Export into CSV data
boolean result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima);
assertTrue(result);
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima);
assertEquals(ImportExportResult.Success, result);
outStream.close();
ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray());
// Import the CSV data on top of the existing database
result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, DataFormat.Catima);
assertTrue(result);
result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, DataFormat.Catima, null);
assertEquals(ImportExportResult.Success, result);
assertEquals(NUM_CARDS, db.getLoyaltyCardCount());
@@ -517,8 +512,8 @@ public class ImportExportTest
OutputStreamWriter outStream = new OutputStreamWriter(outData);
// Export data to CSV format
boolean result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima);
assertTrue(result);
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima);
assertEquals(ImportExportResult.Success, result);
TestHelpers.getEmptyDb(activity);
@@ -531,8 +526,8 @@ public class ImportExportTest
ByteArrayInputStream inData = new ByteArrayInputStream((outData.toString() + corruptEntry).getBytes());
// Attempt to import the data
result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, format);
assertEquals(false, result);
result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, format, null);
assertEquals(ImportExportResult.GenericFailure, result);
assertEquals(0, db.getLoyaltyCardCount());
@@ -542,11 +537,11 @@ public class ImportExportTest
class TestTaskCompleteListener implements ImportExportTask.TaskCompleteListener
{
Boolean success;
ImportExportResult result;
public void onTaskComplete(boolean success)
public void onTaskComplete(ImportExportResult result, DataFormat dataFormat)
{
this.success = success;
this.result = result;
}
}
@@ -572,8 +567,8 @@ public class ImportExportTest
Robolectric.flushBackgroundThreadScheduler();
// Check that the listener was executed
assertNotNull(listener.success);
assertEquals(true, listener.success);
assertNotNull(listener.result);
assertEquals(ImportExportResult.Success, listener.result);
TestHelpers.getEmptyDb(activity);
@@ -583,15 +578,15 @@ public class ImportExportTest
FileInputStream fileStream = new FileInputStream(exportFile);
task = new ImportExportTask(activity, DataFormat.Catima, fileStream, listener);
task = new ImportExportTask(activity, DataFormat.Catima, fileStream, null, listener);
task.execute();
// Actually run the task to completion
Robolectric.flushBackgroundThreadScheduler();
// Check that the listener was executed
assertNotNull(listener.success);
assertEquals(true, listener.success);
assertNotNull(listener.result);
assertEquals(ImportExportResult.Success, listener.result);
assertEquals(NUM_CARDS, db.getLoyaltyCardCount());
@@ -617,8 +612,8 @@ public class ImportExportTest
ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
// Import the CSV data
boolean result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima);
assertTrue(result);
ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null);
assertEquals(ImportExportResult.Success, result);
assertEquals(1, db.getLoyaltyCardCount());
LoyaltyCard card = db.getLoyaltyCard(1);
@@ -655,8 +650,8 @@ public class ImportExportTest
ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
// Import the CSV data
boolean result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima);
assertTrue(result);
ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null);
assertEquals(ImportExportResult.Success, result);
assertEquals(1, db.getLoyaltyCardCount());
LoyaltyCard card = db.getLoyaltyCard(1);
@@ -693,8 +688,8 @@ public class ImportExportTest
ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
// Import the CSV data
boolean result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima);
assertEquals(false, result);
ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null);
assertEquals(ImportExportResult.GenericFailure, result);
assertEquals(0, db.getLoyaltyCardCount());
TestHelpers.getEmptyDb(activity);
@@ -718,8 +713,8 @@ public class ImportExportTest
ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
// Import the CSV data
boolean result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima);
assertEquals(true, result);
ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null);
assertEquals(ImportExportResult.Success, result);
assertEquals(1, db.getLoyaltyCardCount());
LoyaltyCard card = db.getLoyaltyCard(1);
@@ -756,8 +751,8 @@ public class ImportExportTest
ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
// Import the CSV data
boolean result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima);
assertEquals(true, result);
ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null);
assertEquals(ImportExportResult.Success, result);
assertEquals(1, db.getLoyaltyCardCount());
LoyaltyCard card = db.getLoyaltyCard(1);
@@ -794,8 +789,8 @@ public class ImportExportTest
ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
// Import the CSV data
boolean result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima);
assertEquals(true, result);
ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null);
assertEquals(ImportExportResult.Success, result);
assertEquals(1, db.getLoyaltyCardCount());
LoyaltyCard card = db.getLoyaltyCard(1);
@@ -832,8 +827,8 @@ public class ImportExportTest
ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
// Import the CSV data
boolean result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima);
assertTrue(result);
ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null);
assertEquals(ImportExportResult.Success, result);
assertEquals(1, db.getLoyaltyCardCount());
csvText = "";
@@ -851,8 +846,8 @@ public class ImportExportTest
inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
// Import the CSV data
result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima);
assertTrue(result);
result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null);
assertEquals(ImportExportResult.Success, result);
assertEquals(1, db.getLoyaltyCardCount());
LoyaltyCard card = db.getLoyaltyCard(1);
@@ -941,8 +936,8 @@ public class ImportExportTest
ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8));
// Import the CSV data
boolean result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima);
assertEquals(true, result);
ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null);
assertEquals(ImportExportResult.Success, result);
assertEquals(7, db.getLoyaltyCardCount());
assertEquals(3, db.getGroupCount());
@@ -1082,8 +1077,8 @@ public class ImportExportTest
InputStream inputStream = getClass().getResourceAsStream("fidme-export-request-2021-06-29-17-28-20.zip");
// Import the Fidme data
boolean result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Fidme);
assertTrue(result);
ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Fidme, null);
assertEquals(ImportExportResult.Success, result);
assertEquals(3, db.getLoyaltyCardCount());
LoyaltyCard card = db.getLoyaltyCard(1);
@@ -1125,6 +1120,69 @@ public class ImportExportTest
TestHelpers.getEmptyDb(activity);
}
@Test
public void importStocard() throws IOException {
InputStream inputStream = getClass().getResourceAsStream("50e33e49-cfa0-49ad-a297-c1d655f72b01-sync.zip");
// Import the Stocard data
ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Stocard, null);
assertEquals(ImportExportResult.BadPassword, result);
assertEquals(0, db.getLoyaltyCardCount());
inputStream = getClass().getResourceAsStream("50e33e49-cfa0-49ad-a297-c1d655f72b01-sync.zip");
result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Stocard, "da811b40a4dac56f0cbb2d99b21bbb9a".toCharArray());
assertEquals(ImportExportResult.Success, result);
assertEquals(3, db.getLoyaltyCardCount());
LoyaltyCard card = db.getLoyaltyCard(1);
assertEquals("GAMMA", card.store);
assertEquals("", card.note);
assertEquals(null, card.expiry);
assertEquals(new BigDecimal("0"), card.balance);
assertEquals(null, card.balanceType);
assertEquals("55555", card.cardId);
assertEquals(null, card.barcodeId);
assertEquals(null, card.barcodeType);
assertEquals(0, card.starStatus);
assertNull(Utils.retrieveCardImage(activity.getApplicationContext(), 1, true));
assertNull(Utils.retrieveCardImage(activity.getApplicationContext(), 1, false));
card = db.getLoyaltyCard(2);
assertEquals("Air Miles", card.store);
assertEquals("szjsbs", card.note);
assertEquals(null, card.expiry);
assertEquals(new BigDecimal("0"), card.balance);
assertEquals(null, card.balanceType);
assertEquals("7649484", card.cardId);
assertEquals(null, card.barcodeId);
assertEquals(null, card.barcodeType);
assertEquals(0, card.starStatus);
assertTrue(BitmapFactory.decodeStream(getClass().getResourceAsStream("stocard-front.jpg")).sameAs(Utils.retrieveCardImage(activity.getApplicationContext(), 2, true)));
assertTrue(BitmapFactory.decodeStream(getClass().getResourceAsStream("stocard-back.jpg")).sameAs(Utils.retrieveCardImage(activity.getApplicationContext(), 2, false)));
card = db.getLoyaltyCard(3);
assertEquals("", card.store);
assertEquals("", card.note);
assertEquals(null, card.expiry);
assertEquals(new BigDecimal("0"), card.balance);
assertEquals(null, card.balanceType);
assertEquals("(01)09010374000019(21)02097564604859211217(10)01231287693", card.cardId);
assertEquals(null, card.barcodeId);
assertEquals(BarcodeFormat.RSS_EXPANDED, card.barcodeType);
assertEquals(0, card.starStatus);
assertNull(Utils.retrieveCardImage(activity.getApplicationContext(), 3, true));
assertNull(Utils.retrieveCardImage(activity.getApplicationContext(), 3, false));
TestHelpers.getEmptyDb(activity);
}
@Test
public void importVoucherVault() throws IOException, FormatException, JSONException, ParseException {
String jsonText = "[\n" +
@@ -1153,8 +1211,8 @@ public class ImportExportTest
ByteArrayInputStream inputStream = new ByteArrayInputStream(jsonText.getBytes(StandardCharsets.UTF_8));
// Import the Voucher Vault data
boolean result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.VoucherVault);
assertTrue(result);
ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.VoucherVault, null);
assertEquals(ImportExportResult.Success, result);
assertEquals(2, db.getLoyaltyCardCount());
LoyaltyCard card = db.getLoyaltyCard(1);

View File

@@ -4,7 +4,6 @@ import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Color;
import android.view.View;
import android.widget.ImageView;
@@ -29,11 +28,9 @@ import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.RecyclerView;
import protect.card_locker.preferences.Settings;
import static android.os.Looper.getMainLooper;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.robolectric.Shadows.shadowOf;
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 23)

View File

@@ -9,9 +9,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
@@ -57,7 +55,6 @@ import static android.os.Looper.getMainLooper;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.robolectric.Shadows.shadowOf;

View File

@@ -3,11 +3,9 @@ package protect.card_locker;
import android.app.Activity;
import android.content.ComponentName;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.graphics.Color;
import android.view.Menu;
import android.view.View;
import android.widget.ListView;
import android.widget.TextView;
import com.google.android.material.tabs.TabLayout;
@@ -20,7 +18,6 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.android.controller.ActivityController;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowActivity;
import org.w3c.dom.Text;
import java.math.BigDecimal;
import java.util.ArrayList;

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -9,7 +9,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.3'
classpath 'com.android.tools.build:gradle:4.2.2'
classpath 'gradle.plugin.com.github.spotbugs.snom:spotbugs-gradle-plugin:4.7.0'
// NOTE: Do not place your application dependencies here; they belong