diff --git a/app/src/main/java/protect/card_locker/ImportExportActivity.java b/app/src/main/java/protect/card_locker/ImportExportActivity.java index 46335a113..cf1f05c0b 100644 --- a/app/src/main/java/protect/card_locker/ImportExportActivity.java +++ b/app/src/main/java/protect/card_locker/ImportExportActivity.java @@ -78,8 +78,8 @@ public class ImportExportActivity extends AppCompatActivity // Check that there is a file manager available final Intent intentCreateDocumentAction = new Intent(Intent.ACTION_CREATE_DOCUMENT); intentCreateDocumentAction.addCategory(Intent.CATEGORY_OPENABLE); - intentCreateDocumentAction.setType("text/csv"); - intentCreateDocumentAction.putExtra(Intent.EXTRA_TITLE, "Catima.csv"); + intentCreateDocumentAction.setType("application/zip"); + intentCreateDocumentAction.putExtra(Intent.EXTRA_TITLE, "Catima.zip"); Button exportButton = findViewById(R.id.exportButton); exportButton.setOnClickListener(new View.OnClickListener() diff --git a/app/src/main/java/protect/card_locker/ImportExportTask.java b/app/src/main/java/protect/card_locker/ImportExportTask.java index a7acf8068..f76163cbf 100644 --- a/app/src/main/java/protect/card_locker/ImportExportTask.java +++ b/app/src/main/java/protect/card_locker/ImportExportTask.java @@ -77,7 +77,7 @@ class ImportExportTask extends AsyncTask try { OutputStreamWriter writer = new OutputStreamWriter(stream, Charset.forName("UTF-8")); - result = MultiFormatExporter.exportData(context, db, writer, format); + result = MultiFormatExporter.exportData(context, db, stream, format); writer.close(); } catch (IOException e) diff --git a/app/src/main/java/protect/card_locker/Utils.java b/app/src/main/java/protect/card_locker/Utils.java index 5446c9648..c920c70e1 100644 --- a/app/src/main/java/protect/card_locker/Utils.java +++ b/app/src/main/java/protect/card_locker/Utils.java @@ -33,6 +33,7 @@ import java.util.Currency; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; +import java.util.List; import androidx.core.graphics.ColorUtils; @@ -234,30 +235,6 @@ public class Utils { return bos.toByteArray(); } - static public Bitmap byteArrayToBitmap(byte[] byteArray) { - if (byteArray == null) { - return null; - } - - return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length); - } - - static public String bitmapToBase64(Bitmap bitmap) { - if (bitmap == null) { - return null; - } - - return Base64.encodeToString(bitmapToByteArray(bitmap), Base64.URL_SAFE); - } - - static public Bitmap base64ToBitmap(String base64) { - if (base64 == null) { - return null; - } - - return byteArrayToBitmap(Base64.decode(base64, Base64.URL_SAFE)); - } - static public Bitmap resizeBitmap(Bitmap bitmap) { if (bitmap == null) { return null; @@ -307,7 +284,7 @@ public class Utils { return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); } - static private String getCardImageFileName(int loyaltyCardId, boolean front) { + static public String getCardImageFileName(int loyaltyCardId, boolean front) { StringBuilder cardImageFileNameBuilder = new StringBuilder(); cardImageFileNameBuilder.append("card_"); @@ -323,23 +300,25 @@ public class Utils { return cardImageFileNameBuilder.toString(); } - static public void saveCardImage(Context context, Bitmap bitmap, int loyaltyCardId, boolean front) throws FileNotFoundException { - String fileName = getCardImageFileName(loyaltyCardId, front); - + static public void saveCardImage(Context context, Bitmap bitmap, String fileName) throws FileNotFoundException { if (bitmap == null) { context.deleteFile(fileName); return; } - FileOutputStream out = context.openFileOutput(getCardImageFileName(loyaltyCardId, front), Context.MODE_PRIVATE); + FileOutputStream out = context.openFileOutput(fileName, Context.MODE_PRIVATE); bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); } - static public Bitmap retrieveCardImage(Context context, int loyaltyCardId, boolean front) { + static public void saveCardImage(Context context, Bitmap bitmap, int loyaltyCardId, boolean front) throws FileNotFoundException { + saveCardImage(context, bitmap, getCardImageFileName(loyaltyCardId, front)); + } + + static public Bitmap retrieveCardImage(Context context, String fileName) { FileInputStream in; try { - in = context.openFileInput(getCardImageFileName(loyaltyCardId, front)); + in = context.openFileInput(fileName); } catch (FileNotFoundException e) { return null; } @@ -347,11 +326,19 @@ public class Utils { return BitmapFactory.decodeStream(in); } - static public Object hashmapGetOrDefault(HashMap hashMap, String key, Object defaultValue) { - Object value = hashMap.get(key); + static public Bitmap retrieveCardImage(Context context, int loyaltyCardId, boolean front) { + return retrieveCardImage(context, getCardImageFileName(loyaltyCardId, front)); + } + + static public Object hashmapGetOrDefault(HashMap hashMap, Object key, Object defaultValue, Class keyType) { + Object value = hashMap.get(keyType.cast(key)); if (value == null) { return defaultValue; } return value; } + + static public Object hashmapGetOrDefault(HashMap hashMap, String key, Object defaultValue) { + return hashmapGetOrDefault(hashMap, key, defaultValue, String.class); + } } diff --git a/app/src/main/java/protect/card_locker/ZipUtils.java b/app/src/main/java/protect/card_locker/ZipUtils.java new file mode 100644 index 000000000..50f4e5bab --- /dev/null +++ b/app/src/main/java/protect/card_locker/ZipUtils.java @@ -0,0 +1,36 @@ +package protect.card_locker; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; + +import net.lingala.zip4j.io.inputstream.ZipInputStream; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +public class ZipUtils { + static public 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(); + } + + static public Bitmap readImage(ZipInputStream zipInputStream) { + return BitmapFactory.decodeStream(zipInputStream); + } + + static public JSONObject readJSON(ZipInputStream zipInputStream) throws IOException, JSONException { + return new JSONObject(read(zipInputStream)); + } +} diff --git a/app/src/main/java/protect/card_locker/importexport/CSVHelpers.java b/app/src/main/java/protect/card_locker/importexport/CSVHelpers.java index b8b96ed82..74dd8f94e 100644 --- a/app/src/main/java/protect/card_locker/importexport/CSVHelpers.java +++ b/app/src/main/java/protect/card_locker/importexport/CSVHelpers.java @@ -1,38 +1,10 @@ package protect.card_locker.importexport; -import android.graphics.Bitmap; - import org.apache.commons.csv.CSVRecord; import protect.card_locker.FormatException; -import protect.card_locker.Utils; public class CSVHelpers { - static String IMAGE_FRONT = "frontimage"; - static String IMAGE_BACK = "backimage"; - - /** - * Extract an image from the items array. The index into the array - * is determined by looking up the index in the fields map using the - * "key" as the key. If no such key exists, defaultValue is returned - * if it is not null. Otherwise, a FormatException is thrown. - */ - static Bitmap extractImage(String key, CSVRecord record) - { - if(record.isMapped(key)) - { - String value = record.get(key); - - if (value.isEmpty()) { - return null; - } - - return Utils.base64ToBitmap(value); - } - - return null; - } - /** * Extract a string from the items array. The index into the array * is determined by looking up the index in the fields map using the diff --git a/app/src/main/java/protect/card_locker/importexport/CsvExporter.java b/app/src/main/java/protect/card_locker/importexport/CatimaExporter.java similarity index 51% rename from app/src/main/java/protect/card_locker/importexport/CsvExporter.java rename to app/src/main/java/protect/card_locker/importexport/CatimaExporter.java index f5859b867..e8b3c5522 100644 --- a/app/src/main/java/protect/card_locker/importexport/CsvExporter.java +++ b/app/src/main/java/protect/card_locker/importexport/CatimaExporter.java @@ -2,12 +2,26 @@ package protect.card_locker.importexport; import android.content.Context; import android.database.Cursor; +import android.graphics.Bitmap; + +import net.lingala.zip4j.ZipFile; +import net.lingala.zip4j.io.outputstream.ZipOutputStream; +import net.lingala.zip4j.model.ZipParameters; +import net.lingala.zip4j.util.InternalZipConstants; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVPrinter; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.util.zip.ZipEntry; import protect.card_locker.DBHelper; import protect.card_locker.Group; @@ -18,10 +32,65 @@ import protect.card_locker.Utils; * Class for exporting the database into CSV (Comma Separate Values) * format. */ -public class CsvExporter implements Exporter +public class CatimaExporter implements Exporter { - public void exportData(Context context, DBHelper db, OutputStreamWriter output) throws IOException, InterruptedException + public void exportData(Context context, DBHelper db, OutputStream output) throws IOException, InterruptedException { + // Necessary vars + int readLen; + byte[] readBuffer = new byte[InternalZipConstants.BUFF_SIZE]; + + // Create zip output stream + ZipOutputStream zipOutputStream = new ZipOutputStream(output); + + // Generate CSV + ByteArrayOutputStream catimaOutputStream = new ByteArrayOutputStream(); + OutputStreamWriter catimaOutputStreamWriter = new OutputStreamWriter(catimaOutputStream, StandardCharsets.UTF_8); + writeCSV(db, catimaOutputStreamWriter); + + // Add CSV to zip file + ZipParameters csvZipParameters = new ZipParameters(); + csvZipParameters.setFileNameInZip("catima.csv"); + zipOutputStream.putNextEntry(csvZipParameters); + InputStream csvInputStream = new ByteArrayInputStream(catimaOutputStream.toByteArray()); + while ((readLen = csvInputStream.read(readBuffer)) != -1) { + zipOutputStream.write(readBuffer, 0, readLen); + } + zipOutputStream.closeEntry(); + + // Loop over all cards again + Cursor cardCursor = db.getLoyaltyCardCursor(); + while(cardCursor.moveToNext()) + { + // For each card + LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor); + + // Prepare looping over both front and back image + boolean[] frontValues = new boolean[2]; + frontValues[0] = true; + frontValues[1] = false; + + // For each image + for (boolean front : frontValues) { + // If it exists, add to the .zip file + Bitmap image = Utils.retrieveCardImage(context, card.id, front); + if (image != null) { + ZipParameters imageZipParameters = new ZipParameters(); + imageZipParameters.setFileNameInZip(Utils.getCardImageFileName(card.id, front)); + zipOutputStream.putNextEntry(imageZipParameters); + InputStream imageInputStream = new ByteArrayInputStream(Utils.bitmapToByteArray(image)); + while ((readLen = imageInputStream.read(readBuffer)) != -1) { + zipOutputStream.write(readBuffer, 0, readLen); + } + zipOutputStream.closeEntry(); + } + } + } + + zipOutputStream.close(); + } + + private void writeCSV(DBHelper db, OutputStreamWriter output) throws IOException, InterruptedException { CSVPrinter printer = new CSVPrinter(output, CSVFormat.RFC4180); // Print the version @@ -62,9 +131,7 @@ public class CsvExporter implements Exporter DBHelper.LoyaltyCardDbIds.BARCODE_ID, DBHelper.LoyaltyCardDbIds.BARCODE_TYPE, DBHelper.LoyaltyCardDbIds.HEADER_COLOR, - DBHelper.LoyaltyCardDbIds.STAR_STATUS, - CSVHelpers.IMAGE_FRONT, - CSVHelpers.IMAGE_BACK); + DBHelper.LoyaltyCardDbIds.STAR_STATUS); Cursor cardCursor = db.getLoyaltyCardCursor(); @@ -82,9 +149,7 @@ public class CsvExporter implements Exporter card.barcodeId, card.barcodeType, card.headerColor, - card.starStatus, - Utils.bitmapToBase64(Utils.retrieveCardImage(context, card.id, true)), - Utils.bitmapToBase64(Utils.retrieveCardImage(context, card.id, false))); + card.starStatus); if(Thread.currentThread().isInterrupted()) { diff --git a/app/src/main/java/protect/card_locker/importexport/CsvImporter.java b/app/src/main/java/protect/card_locker/importexport/CatimaImporter.java similarity index 88% rename from app/src/main/java/protect/card_locker/importexport/CsvImporter.java rename to app/src/main/java/protect/card_locker/importexport/CatimaImporter.java index e233ee192..4ba4a6275 100644 --- a/app/src/main/java/protect/card_locker/importexport/CsvImporter.java +++ b/app/src/main/java/protect/card_locker/importexport/CatimaImporter.java @@ -5,11 +5,15 @@ 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; import java.io.BufferedReader; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -24,6 +28,7 @@ import protect.card_locker.DBHelper; import protect.card_locker.FormatException; import protect.card_locker.Group; import protect.card_locker.Utils; +import protect.card_locker.ZipUtils; /** * Class for importing a database from CSV (Comma Separate Values) @@ -32,10 +37,33 @@ import protect.card_locker.Utils; * 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 CsvImporter implements Importer +public class CatimaImporter implements Importer { - public void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, InterruptedException - { + public void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, InterruptedException { + // First, check if this is a zip file + ZipInputStream zipInputStream = new ZipInputStream(input); + LocalFileHeader localFileHeader = zipInputStream.getNextEntry(); + + if (localFileHeader == null) { + // This is not a zip file, try importing as bare CSV + input.reset(); + importCSV(context, db, input); + return; + } + + importZipFile(context, db, zipInputStream, localFileHeader); + } + + public void importZipFile(Context context, DBHelper db, ZipInputStream input, LocalFileHeader localFileHeader) throws IOException, FormatException, InterruptedException { + String fileName = localFileHeader.getFileName(); + if (fileName.equals("catima.csv")) { + importCSV(context, db, new ByteArrayInputStream(ZipUtils.read(input).getBytes(StandardCharsets.UTF_8))); + } else { + Utils.saveCardImage(context, ZipUtils.readImage(input), fileName); + } + } + + public void importCSV(Context context, DBHelper db, InputStream input) throws IOException, FormatException, InterruptedException { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)); bufferedReader.mark(100); @@ -278,9 +306,6 @@ public class CsvImporter implements Importer if (starStatus != 1) starStatus = 0; helper.insertLoyaltyCard(database, id, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starStatus); - - Utils.saveCardImage(context, CSVHelpers.extractImage(CSVHelpers.IMAGE_FRONT, record), id, true); - Utils.saveCardImage(context, CSVHelpers.extractImage(CSVHelpers.IMAGE_BACK, record), id, false); } /** diff --git a/app/src/main/java/protect/card_locker/importexport/Exporter.java b/app/src/main/java/protect/card_locker/importexport/Exporter.java index 37a341fc1..1b4b48322 100644 --- a/app/src/main/java/protect/card_locker/importexport/Exporter.java +++ b/app/src/main/java/protect/card_locker/importexport/Exporter.java @@ -3,6 +3,7 @@ package protect.card_locker.importexport; import android.content.Context; import java.io.IOException; +import java.io.OutputStream; import java.io.OutputStreamWriter; import protect.card_locker.DBHelper; @@ -17,5 +18,5 @@ public interface Exporter * Export the database to the output stream in a given format. * @throws IOException */ - void exportData(Context context, DBHelper db, OutputStreamWriter output) throws IOException, InterruptedException; + void exportData(Context context, DBHelper db, OutputStream output) throws IOException, InterruptedException; } diff --git a/app/src/main/java/protect/card_locker/importexport/MultiFormatExporter.java b/app/src/main/java/protect/card_locker/importexport/MultiFormatExporter.java index f020370d2..56ca28ece 100644 --- a/app/src/main/java/protect/card_locker/importexport/MultiFormatExporter.java +++ b/app/src/main/java/protect/card_locker/importexport/MultiFormatExporter.java @@ -4,6 +4,7 @@ import android.content.Context; import android.util.Log; import java.io.IOException; +import java.io.OutputStream; import java.io.OutputStreamWriter; import protect.card_locker.DBHelper; @@ -22,14 +23,14 @@ public class MultiFormatExporter * another ImportExportResult otherwise. If not Success, partial data may have been * written to the output stream, and it should be discarded. */ - public static ImportExportResult exportData(Context context, DBHelper db, OutputStreamWriter output, DataFormat format) + public static ImportExportResult exportData(Context context, DBHelper db, OutputStream output, DataFormat format) { Exporter exporter = null; switch(format) { case Catima: - exporter = new CsvExporter(); + exporter = new CatimaExporter(); break; default: Log.e(TAG, "Failed to export data, unknown format " + format.name()); diff --git a/app/src/main/java/protect/card_locker/importexport/MultiFormatImporter.java b/app/src/main/java/protect/card_locker/importexport/MultiFormatImporter.java index 835bb26df..3463f1073 100644 --- a/app/src/main/java/protect/card_locker/importexport/MultiFormatImporter.java +++ b/app/src/main/java/protect/card_locker/importexport/MultiFormatImporter.java @@ -36,7 +36,7 @@ public class MultiFormatImporter switch(format) { case Catima: - importer = new CsvImporter(); + importer = new CatimaImporter(); break; case Fidme: importer = new FidmeImporter(); @@ -60,7 +60,7 @@ public class MultiFormatImporter { return ImportExportResult.BadPassword; } - catch(IOException | FormatException | InterruptedException | JSONException | ParseException e) + catch(IOException | FormatException | InterruptedException | JSONException | ParseException | NullPointerException e) { Log.e(TAG, "Failed to import data", e); } diff --git a/app/src/main/java/protect/card_locker/importexport/StocardImporter.java b/app/src/main/java/protect/card_locker/importexport/StocardImporter.java index 7a9a416ed..a63b26bf5 100644 --- a/app/src/main/java/protect/card_locker/importexport/StocardImporter.java +++ b/app/src/main/java/protect/card_locker/importexport/StocardImporter.java @@ -30,6 +30,7 @@ import java.util.List; import protect.card_locker.DBHelper; import protect.card_locker.FormatException; import protect.card_locker.Utils; +import protect.card_locker.ZipUtils; /** * Class for importing a database from CSV (Comma Separate Values) @@ -83,7 +84,7 @@ public class StocardImporter implements Importer if (nameParts.length == cardBaseName.length + 1) { // Ignore the .txt file if (fileName.endsWith(".json")) { - JSONObject jsonObject = readJSON(zipInputStream); + JSONObject jsonObject = ZipUtils.readJSON(zipInputStream); loyaltyCardHashMap = appendToLoyaltyCardHashMap( loyaltyCardHashMap, @@ -115,7 +116,7 @@ public class StocardImporter implements Importer loyaltyCardHashMap, cardName, "note", - readJSON(zipInputStream) + ZipUtils.readJSON(zipInputStream) .getString("content") ); } else if (fileName.endsWith("/images/front.png")) { @@ -123,14 +124,14 @@ public class StocardImporter implements Importer loyaltyCardHashMap, cardName, "frontImage", - readImage(zipInputStream) + ZipUtils.readImage(zipInputStream) ); } else if (fileName.endsWith("/images/back.png")) { loyaltyCardHashMap = appendToLoyaltyCardHashMap( loyaltyCardHashMap, cardName, "backImage", - readImage(zipInputStream) + ZipUtils.readImage(zipInputStream) ); } } @@ -188,24 +189,6 @@ public class StocardImporter implements Importer 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> appendToLoyaltyCardHashMap(HashMap> loyaltyCardHashMap, String cardID, String key, Object value) { HashMap loyaltyCardData = loyaltyCardHashMap.get(cardID); if (loyaltyCardData == null) { @@ -220,7 +203,7 @@ public class StocardImporter implements Importer private HashMap 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); + JSONObject jsonObject = ZipUtils.readJSON(zipInputStream); JSONArray providerIdList = jsonObject.getJSONArray("provider_id_list"); JSONArray providerList = jsonObject.getJSONArray("provider_list"); diff --git a/app/src/main/res/drawable/ic_android_launcher.xml b/app/src/main/res/drawable/ic_android_launcher.xml deleted file mode 100644 index 9788de509..000000000 --- a/app/src/main/res/drawable/ic_android_launcher.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_baseline_calendar_today_24.xml b/app/src/main/res/drawable/ic_baseline_calendar_today_24.xml deleted file mode 100644 index b6caf720f..000000000 --- a/app/src/main/res/drawable/ic_baseline_calendar_today_24.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/list_divider.xml b/app/src/main/res/drawable/list_divider.xml deleted file mode 100644 index 5afbb0145..000000000 --- a/app/src/main/res/drawable/list_divider.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/test/java/protect/card_locker/ImportExportTest.java b/app/src/test/java/protect/card_locker/ImportExportTest.java index d464a6905..f787152a7 100644 --- a/app/src/test/java/protect/card_locker/ImportExportTest.java +++ b/app/src/test/java/protect/card_locker/ImportExportTest.java @@ -29,6 +29,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; @@ -38,6 +39,7 @@ import java.util.Arrays; import java.util.Calendar; import java.util.Currency; import java.util.Date; +import java.util.HashMap; import java.util.List; import androidx.core.content.res.ResourcesCompat; @@ -333,7 +335,7 @@ public class ImportExportTest OutputStreamWriter outStream = new OutputStreamWriter(outData); // Export data to CSV format - ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima); + ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima); assertEquals(ImportExportResult.Success, result); outStream.close(); @@ -364,7 +366,7 @@ public class ImportExportTest OutputStreamWriter outStream = new OutputStreamWriter(outData); // Export data to CSV format - ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima); + ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima); assertEquals(ImportExportResult.Success, result); outStream.close(); @@ -437,7 +439,7 @@ public class ImportExportTest OutputStreamWriter outStream = new OutputStreamWriter(outData); // Export data to CSV format - ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima); + ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima); assertEquals(ImportExportResult.Success, result); outStream.close(); @@ -481,7 +483,7 @@ public class ImportExportTest OutputStreamWriter outStream = new OutputStreamWriter(outData); // Export into CSV data - ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima); + ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima); assertEquals(ImportExportResult.Success, result); outStream.close(); @@ -512,7 +514,7 @@ public class ImportExportTest OutputStreamWriter outStream = new OutputStreamWriter(outData); // Export data to CSV format - ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima); + ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima); assertEquals(ImportExportResult.Success, result); TestHelpers.getEmptyDb(activity); @@ -867,46 +869,106 @@ public class ImportExportTest } @Test - public void exportV2() throws FileNotFoundException + public void exportImportV2Zip() throws FileNotFoundException { - db.insertGroup("Example"); - + // Prepare images BitmapDrawable launcher = (BitmapDrawable) ResourcesCompat.getDrawableForDensity(activity.getResources(), R.mipmap.ic_launcher, DisplayMetrics.DENSITY_XXXHIGH, activity.getTheme()); BitmapDrawable roundLauncher = (BitmapDrawable) ResourcesCompat.getDrawableForDensity(activity.getResources(), R.mipmap.ic_launcher_round, DisplayMetrics.DENSITY_XXXHIGH, activity.getTheme()); - Bitmap frontImage = launcher.getBitmap(); - Bitmap backImage = roundLauncher.getBitmap(); + Bitmap launcherBitmap = launcher.getBitmap(); + Bitmap roundLauncherBitmap = roundLauncher.getBitmap(); - int loyaltyCard = (int) db.insertLoyaltyCard("Card 1", "Note 1", new Date(1618053234), new BigDecimal("100"), Currency.getInstance("USD"), "1234", "5432", BarcodeFormat.QR_CODE, 1, 0); + // Set up cards and groups + HashMap loyaltyCardHashMap = new HashMap<>(); + HashMap> loyaltyCardGroups = new HashMap<>(); + HashMap loyaltyCardFrontImages = new HashMap<>(); + HashMap loyaltyCardBackImages = new HashMap<>(); - Utils.saveCardImage(activity.getApplicationContext(), Utils.resizeBitmap(frontImage), loyaltyCard, true); - Utils.saveCardImage(activity.getApplicationContext(), Utils.resizeBitmap(backImage), loyaltyCard, false); + // Create card 1 + int loyaltyCardId = (int) db.insertLoyaltyCard("Card 1", "Note 1", new Date(1618053234), new BigDecimal("100"), Currency.getInstance("USD"), "1234", "5432", BarcodeFormat.QR_CODE, 1, 0); + loyaltyCardHashMap.put(loyaltyCardId, db.getLoyaltyCard(loyaltyCardId)); + db.insertGroup("One"); + List groups = Arrays.asList(db.getGroup("One")); + db.setLoyaltyCardGroups(loyaltyCardId, groups); + loyaltyCardGroups.put(loyaltyCardId, groups); + Utils.saveCardImage(activity.getApplicationContext(), launcherBitmap, loyaltyCardId, true); + Utils.saveCardImage(activity.getApplicationContext(), roundLauncherBitmap, loyaltyCardId, false); + loyaltyCardFrontImages.put(loyaltyCardId, launcherBitmap); + loyaltyCardBackImages.put(loyaltyCardId, roundLauncherBitmap); - db.setLoyaltyCardGroups(loyaltyCard, Arrays.asList(db.getGroup("Example"))); + // Create card 2 + loyaltyCardId = (int) db.insertLoyaltyCard("Card 2", "", null, new BigDecimal(0), null, "123456", null, null, 2, 1); + loyaltyCardHashMap.put(loyaltyCardId, db.getLoyaltyCard(loyaltyCardId)); + // Export everything ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream); + MultiFormatExporter.exportData(activity.getApplicationContext(), db, outputStream, DataFormat.Catima); - MultiFormatExporter.exportData(activity.getApplicationContext(), db, outputStreamWriter, DataFormat.Catima); + // Wipe database + TestHelpers.getEmptyDb(activity); - String outputCsv = "2\r\n" + - "\r\n" + - "_id\r\n" + - "Example\r\n" + - "\r\n" + - "_id,store,note,expiry,balance,balancetype,cardid,barcodeid,barcodetype,headercolor,starstatus,frontimage,backimage\r\n" + - "1,Card 1,Note 1,1618053234,100,USD,1234,5432,QR_CODE,1,0,\"iVBORw0KGgoAAAANSUhEUgAAAgAAAAIAAQAAAADcA-lXAAAANklEQVR42u3BAQEAAACCIP-vbkhA\n" + - "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8G4IAAAFjdVCkAAAAAElFTkSuQmCC\n\",\"iVBORw0KGgoAAAANSUhEUgAAAgAAAAIAAQAAAADcA-lXAAAANklEQVR42u3BAQEAAACCIP-vbkhA\n" + - "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8G4IAAAFjdVCkAAAAAElFTkSuQmCC\n\"\r\n" + - "\r\n" + - "cardId,groupId\r\n" + - "1,Example\r\n"; + // Import everything + MultiFormatImporter.importData(activity.getApplicationContext(), db, new ByteArrayInputStream(outputStream.toByteArray()), DataFormat.Catima, null); - assertEquals(outputCsv, outputStream.toString()); + // Ensure everything is there + assertEquals(loyaltyCardHashMap.size(), db.getLoyaltyCardCount()); + + for (Integer loyaltyCardID : loyaltyCardHashMap.keySet()) { + LoyaltyCard loyaltyCard = loyaltyCardHashMap.get(loyaltyCardID); + + LoyaltyCard dbLoyaltyCard = db.getLoyaltyCard(loyaltyCardID); + + assertEquals(loyaltyCard.id, dbLoyaltyCard.id); + assertEquals(loyaltyCard.store, dbLoyaltyCard.store); + assertEquals(loyaltyCard.note, dbLoyaltyCard.note); + assertEquals(loyaltyCard.expiry, dbLoyaltyCard.expiry); + assertEquals(loyaltyCard.balance, dbLoyaltyCard.balance); + assertEquals(loyaltyCard.cardId, dbLoyaltyCard.cardId); + assertEquals(loyaltyCard.barcodeId, dbLoyaltyCard.barcodeId); + assertEquals(loyaltyCard.starStatus, dbLoyaltyCard.starStatus); + assertEquals(loyaltyCard.barcodeType, dbLoyaltyCard.barcodeType); + assertEquals(loyaltyCard.balanceType, dbLoyaltyCard.balanceType); + assertEquals(loyaltyCard.headerColor, dbLoyaltyCard.headerColor); + + List emptyGroup = new ArrayList<>(); + + assertEquals( + groupsToGroupNames( + (List) Utils.hashmapGetOrDefault( + loyaltyCardGroups, + loyaltyCardID, + emptyGroup, + Integer.class + ) + ), + groupsToGroupNames( + db.getLoyaltyCardGroups( + loyaltyCardID + ) + ) + ); + + Bitmap expectedFrontImage = loyaltyCardFrontImages.get(loyaltyCardID); + Bitmap expectedBackImage = loyaltyCardBackImages.get(loyaltyCardID); + Bitmap actualFrontImage = Utils.retrieveCardImage(activity.getApplicationContext(), Utils.getCardImageFileName(loyaltyCardID, true)); + Bitmap actualBackImage = Utils.retrieveCardImage(activity.getApplicationContext(), Utils.getCardImageFileName(loyaltyCardID, false)); + + if (expectedFrontImage != null) { + assertTrue(expectedFrontImage.sameAs(actualFrontImage)); + } else { + assertNull(actualFrontImage); + } + + if (expectedBackImage != null) { + assertTrue(expectedBackImage.sameAs(actualBackImage)); + } else { + assertNull(actualBackImage); + } + } } @Test - public void importV2() + public void importV2CSV() { String csvText = "2\n" + "\n" + @@ -915,16 +977,14 @@ public class ImportExportTest "Food\n" + "Fashion\n" + "\n" + - "_id,store,note,expiry,balance,balancetype,cardid,barcodeid,headercolor,barcodetype,starstatus,frontimage,backimage\n" + - "1,Card 1,Note 1,1618053234,100,USD,1234,5432,1,QR_CODE,0,\"iVBORw0KGgoAAAANSUhEUgAAAgAAAAIAAQAAAADcA-lXAAAANklEQVR42u3BAQEAAACCIP-vbkhA\n" + - "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8G4IAAAFjdVCkAAAAAElFTkSuQmCC\n\",\"iVBORw0KGgoAAAANSUhEUgAAAgAAAAIAAQAAAADcA-lXAAAANklEQVR42u3BAQEAAACCIP-vbkhA\n" + - "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8G4IAAAFjdVCkAAAAAElFTkSuQmCC\n\"\r\n" + - "8,Clothes Store,Note about store,,0,,a,,-5317,,0,,\n" + - "2,Department Store,,1618041729,0,,A,,-9977996,,0,,\n" + - "3,Grocery Store,,,150,,dhd,,-9977996,,0,,\n" + - "4,Pharmacy,,,0,,dhshsvshs,,-10902850,,1,,\n" + - "5,Restaurant,Note about restaurant here,,0,,98765432,23456,-10902850,CODE_128,0,,\n" + - "6,Shoe Store,,,12.50,EUR,a,-5317,,AZTEC,0,,\n" + + "_id,store,note,expiry,balance,balancetype,cardid,barcodeid,headercolor,barcodetype,starstatus\n" + + "1,Card 1,Note 1,1618053234,100,USD,1234,5432,1,QR_CODE,0,\r\n" + + "8,Clothes Store,Note about store,,0,,a,,-5317,,0,\n" + + "2,Department Store,,1618041729,0,,A,,-9977996,,0,\n" + + "3,Grocery Store,,,150,,dhd,,-9977996,,0,\n" + + "4,Pharmacy,,,0,,dhshsvshs,,-10902850,,1,\n" + + "5,Restaurant,Note about restaurant here,,0,,98765432,23456,-10902850,CODE_128,0,\n" + + "6,Shoe Store,,,12.50,EUR,a,-5317,,AZTEC,0,\n" + "\n" + "cardId,groupId\n" + "8,Fashion\n" + @@ -958,12 +1018,6 @@ public class ImportExportTest assertEquals(Arrays.asList(8, 6), db.getGroupCardIds("Fashion")); // Check all cards - BitmapDrawable launcher = (BitmapDrawable) ResourcesCompat.getDrawableForDensity(activity.getResources(), R.mipmap.ic_launcher, DisplayMetrics.DENSITY_XXXHIGH, activity.getTheme()); - BitmapDrawable roundLauncher = (BitmapDrawable) ResourcesCompat.getDrawableForDensity(activity.getResources(), R.mipmap.ic_launcher_round, DisplayMetrics.DENSITY_XXXHIGH, activity.getTheme()); - - Bitmap frontImage = launcher.getBitmap(); - Bitmap backImage = roundLauncher.getBitmap(); - LoyaltyCard card1 = db.getLoyaltyCard(1); assertEquals("Card 1", card1.store); @@ -976,8 +1030,8 @@ public class ImportExportTest assertEquals(BarcodeFormat.QR_CODE, card1.barcodeType); assertEquals(1, (long) card1.headerColor); assertEquals(0, card1.starStatus); - assertTrue(Utils.resizeBitmap(frontImage).sameAs(Utils.retrieveCardImage(activity.getApplicationContext(), card1.id, true))); - assertTrue(Utils.resizeBitmap(backImage).sameAs(Utils.retrieveCardImage(activity.getApplicationContext(), card1.id, false))); + assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card1.id, true)); + assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card1.id, false)); LoyaltyCard card8 = db.getLoyaltyCard(8); @@ -1074,7 +1128,7 @@ public class ImportExportTest @Test public void importFidme() { - InputStream inputStream = getClass().getResourceAsStream("fidme-export-request-2021-06-29-17-28-20.zip"); + InputStream inputStream = getClass().getResourceAsStream("fidme.zip"); // Import the Fidme data ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Fidme, null); @@ -1122,14 +1176,14 @@ public class ImportExportTest @Test public void importStocard() throws IOException { - InputStream inputStream = getClass().getResourceAsStream("50e33e49-cfa0-49ad-a297-c1d655f72b01-sync.zip"); + InputStream inputStream = getClass().getResourceAsStream("stocard.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"); + inputStream = getClass().getResourceAsStream("stocard.zip"); result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Stocard, "da811b40a4dac56f0cbb2d99b21bbb9a".toCharArray()); assertEquals(ImportExportResult.Success, result); diff --git a/app/src/test/java/protect/card_locker/TestHelpers.java b/app/src/test/java/protect/card_locker/TestHelpers.java index cccca7bc6..36683f8ae 100644 --- a/app/src/test/java/protect/card_locker/TestHelpers.java +++ b/app/src/test/java/protect/card_locker/TestHelpers.java @@ -1,11 +1,33 @@ package protect.card_locker; import android.app.Activity; +import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.List; + public class TestHelpers { static public DBHelper getEmptyDb(Activity activity) { DBHelper db = new DBHelper(activity); + + // Make sure no files remain + Cursor cursor = db.getLoyaltyCardCursor(); + cursor.moveToFirst(); + while (!cursor.isAfterLast()) { + int cardID = cursor.getColumnIndex(DBHelper.LoyaltyCardDbIds.ID); + + try { + Utils.saveCardImage(activity.getApplicationContext(), null, cardID, true); + } catch (FileNotFoundException ignored) {} + try { + Utils.saveCardImage(activity.getApplicationContext(), null, cardID, false); + } catch (FileNotFoundException ignored) {} + + cursor.moveToNext(); + } + // Make sure DB is empty SQLiteDatabase database = db.getWritableDatabase(); database.execSQL("delete from " + DBHelper.LoyaltyCardDbIds.TABLE); diff --git a/app/src/test/res/protect/card_locker/fidme-export-request-2021-06-29-17-28-20.zip b/app/src/test/res/protect/card_locker/fidme.zip similarity index 100% rename from app/src/test/res/protect/card_locker/fidme-export-request-2021-06-29-17-28-20.zip rename to app/src/test/res/protect/card_locker/fidme.zip diff --git a/app/src/test/res/protect/card_locker/50e33e49-cfa0-49ad-a297-c1d655f72b01-sync.zip b/app/src/test/res/protect/card_locker/stocard.zip similarity index 100% rename from app/src/test/res/protect/card_locker/50e33e49-cfa0-49ad-a297-c1d655f72b01-sync.zip rename to app/src/test/res/protect/card_locker/stocard.zip