Compare commits

...

32 Commits

Author SHA1 Message Date
Sylvia van Os
435ad2cfb3 Fix comment 2020-10-24 15:35:25 +02:00
Sylvia van Os
ef675fe6e5 Merge branch 'master' of github.com:TheLastProject/loyalty-card-locker into feature/pkpass 2020-10-24 15:07:22 +02:00
Sylvia van Os
fe4dc868aa Fix bad merge 2020-02-22 15:00:58 +01:00
Sylvia van Os
52e32caca3 Fix merge typo 2020-02-21 16:25:24 +01:00
Sylvia van Os
d7ef204d0a Merge branch 'master' into feature/pkpass 2020-02-21 15:33:12 +01:00
Sylvia van Os
915e364454 Merge branch 'master' of ssh://github.com/brarcher/loyalty-card-locker into feature/pkpass 2020-01-12 22:50:29 +01:00
Sylvia van Os
da1d1d62e4 Merge branch 'master' of ssh://github.com/brarcher/loyalty-card-locker into feature/pkpass 2020-01-04 20:25:52 +01:00
Sylvia van Os
fd1b86b1cc Fix sharing loyalty card with icon 2019-12-18 13:26:12 +01:00
Sylvia van Os
3f4d0ab5df Grab highest quality icon 2019-12-18 12:18:32 +01:00
Sylvia van Os
1a6cad893d Load icon from Pkpass if exists 2019-12-18 12:15:32 +01:00
Sylvia van Os
2ef4d3f6e0 Fix findBugs performance warning 2019-12-16 17:03:33 +01:00
Sylvia van Os
43148cb12c Merge branch 'master' of ssh://github.com/brarcher/loyalty-card-locker into feature/pkpass 2019-12-16 16:51:25 +01:00
Sylvia van Os
80b45973ac More zip types 2019-12-16 16:45:45 +01:00
Sylvia van Os
ca8243de01 Always fall back to English and then untranslated 2019-12-16 16:34:39 +01:00
Sylvia van Os
b4a532d183 Fix tests 2019-12-16 16:09:56 +01:00
Sylvia van Os
a05f9f6ec9 Parsing fixes 2019-12-16 15:25:27 +01:00
Sylvia van Os
79bba52d93 Load translations (TODO: tests) 2019-12-15 19:10:34 +01:00
Sylvia van Os
04dcee6097 Fix some tests 2019-12-14 21:37:48 +01:00
Sylvia van Os
6cd7a3f5bf Parse extras 2019-12-14 21:00:35 +01:00
Sylvia van Os
09727038ce Don't crash if color is still not valid 2019-12-11 17:19:16 +01:00
Sylvia van Os
896da82b13 Use full .pkpass files for tests 2019-12-11 16:20:32 +01:00
Sylvia van Os
ca93f89e1b Add Eurowings support 2019-12-11 15:32:45 +01:00
Sylvia van Os
9291563cef Fix color parsing 2019-12-11 14:29:41 +01:00
Sylvia van Os
703db472fc Start implementing color parsing 2019-12-10 18:04:12 +01:00
Sylvia van Os
b043320af5 Only try to parse allowed barcode formats 2019-12-10 17:21:04 +01:00
Sylvia van Os
e1f25ed092 Don't crash on unsupported barcode 2019-12-09 19:49:04 +01:00
Sylvia van Os
05d38a0184 XZING uses uppercase typenames 2019-12-09 19:33:17 +01:00
Sylvia van Os
5a046b037e Make linter happy 2019-12-09 17:57:40 +01:00
Sylvia van Os
2f73d510e2 Unit test 2019-12-09 17:55:07 +01:00
Sylvia van Os
2c7cb4769a Parse barcodes if exists 2019-12-09 17:44:13 +01:00
Sylvia van Os
3d543dd23c Use description as note 2019-12-09 17:25:48 +01:00
Sylvia van Os
8637f8d2a1 Initial pkpass support 2019-12-09 17:08:56 +01:00
28 changed files with 1258 additions and 101 deletions

View File

@@ -32,6 +32,11 @@ android {
disable "MissingTranslation"
disable "MissingPrefix"
}
sourceSets {
test {
resources.srcDirs += ['src/test/resources']
}
}
// Starting with Android Studio 3 Robolectric is unable to find resources.
// The following allows it to find the resources.

View File

@@ -49,7 +49,7 @@
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Accepts URIs that begin with "https://github.com/brarcher/loyalty-card-locker/” -->
<!-- Accepts URIs that begin with "https://thelastproject.github.io/Catima/share" or "https://brarcher.github.io/loyalty-card-locker/share-->
<data android:scheme="https"
android:host="@string/intent_import_card_from_url_host"
android:pathPrefix="@string/intent_import_card_from_url_path_prefix" />
@@ -57,6 +57,19 @@
android:host="@string/intent_import_card_from_url_host_old"
android:pathPrefix="@string/intent_import_card_from_url_path_prefix_old" />
</intent-filter>
<intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<!-- Accept pkpass files -->
<data android:scheme="content" android:mimeType="application/octet-stream"/>
<data android:scheme="content" android:mimeType="application/x-compressed"/>
<data android:scheme="content" android:mimeType="application/x-zip-compressed"/>
<data android:scheme="content" android:mimeType="application/zip"/>
<data android:scheme="content" android:mimeType="application/vnd.apple.pkpass"/>
<data android:scheme="content" android:mimeType="application/pkpass"/>
<data android:scheme="content" android:mimeType="application/vndapplepkpass"/>
<data android:scheme="content" android:mimeType="application/vnd-com.apple.pkpass"/>
</intent-filter>
</activity>
<activity
android:name=".BarcodeSelectorActivity"

View File

@@ -4,6 +4,7 @@ import android.database.Cursor;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import org.json.JSONException;
import java.io.IOException;
import java.io.OutputStreamWriter;
@@ -25,7 +26,8 @@ public class CsvDatabaseExporter implements DatabaseExporter
DBHelper.LoyaltyCardDbIds.CARD_ID,
DBHelper.LoyaltyCardDbIds.HEADER_COLOR,
DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR,
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE);
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE,
DBHelper.LoyaltyCardDbIds.EXTRAS);
Cursor cursor = db.getLoyaltyCardCursor();
@@ -33,13 +35,23 @@ public class CsvDatabaseExporter implements DatabaseExporter
{
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cursor);
String extras;
try {
extras = card.extras.toJSON().toString();
}
catch (JSONException ex)
{
throw new IOException(ex);
}
printer.printRecord(card.id,
card.store,
card.note,
card.cardId,
card.headerColor,
card.headerTextColor,
card.barcodeType);
card.barcodeType,
extras);
if(Thread.currentThread().isInterrupted())
{

View File

@@ -1,13 +1,17 @@
package protect.card_locker;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.Normalizer;
/**
* Class for importing a database from CSV (Comma Separate Values)
@@ -142,6 +146,23 @@ public class CsvDatabaseImporter implements DatabaseImporter
headerTextColor = extractInt(DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR, record, true);
}
helper.insertLoyaltyCard(database, id, store, note, cardId, barcodeType, headerColor, headerTextColor);
Bitmap icon = null;
String iconData = extractString(DBHelper.LoyaltyCardDbIds.ICON, record, "");
if(!iconData.isEmpty())
{
icon = DBHelper.convertBitmapBlobToBitmap(iconData.getBytes("UTF-8"));
}
ExtrasHelper extras = new ExtrasHelper();
try
{
extras.fromJSON(new JSONObject(extractString(DBHelper.LoyaltyCardDbIds.EXTRAS, record, "{}")));
helper.insertLoyaltyCard(database, id, store, note, cardId, barcodeType, headerColor, headerTextColor, icon, extras);
}
catch (JSONException ex)
{
throw new FormatException("Invalid JSON in extras field: " + ex);
}
}
}

View File

@@ -6,12 +6,20 @@ import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
public class DBHelper extends SQLiteOpenHelper
{
public static final String DATABASE_NAME = "Catima.db";
public static final int ORIGINAL_DATABASE_VERSION = 1;
public static final int DATABASE_VERSION = 3;
public static final int DATABASE_VERSION = 4;
static class LoyaltyCardDbIds
{
@@ -23,6 +31,8 @@ public class DBHelper extends SQLiteOpenHelper
public static final String HEADER_TEXT_COLOR = "headertextcolor";
public static final String CARD_ID = "cardid";
public static final String BARCODE_TYPE = "barcodetype";
public static final String ICON = "icon";
public static final String EXTRAS = "extras";
}
public DBHelper(Context context)
@@ -30,6 +40,19 @@ public class DBHelper extends SQLiteOpenHelper
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
public static byte[] convertBitmapToBlob(Bitmap bitmap)
{
// https://stackoverflow.com/a/7620610
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 0, byteArrayOutputStream);
return byteArrayOutputStream.toByteArray();
}
public static Bitmap convertBitmapBlobToBitmap(byte[] bytes)
{
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
}
@Override
public void onCreate(SQLiteDatabase db)
{
@@ -41,7 +64,9 @@ public class DBHelper extends SQLiteOpenHelper
LoyaltyCardDbIds.HEADER_COLOR + " INTEGER," +
LoyaltyCardDbIds.HEADER_TEXT_COLOR + " INTEGER," +
LoyaltyCardDbIds.CARD_ID + " TEXT not null," +
LoyaltyCardDbIds.BARCODE_TYPE + " TEXT not null)");
LoyaltyCardDbIds.BARCODE_TYPE + " TEXT not null," +
LoyaltyCardDbIds.ICON + " BLOB," +
LoyaltyCardDbIds.EXTRAS + " TEXT)");
}
@Override
@@ -62,11 +87,21 @@ public class DBHelper extends SQLiteOpenHelper
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.HEADER_TEXT_COLOR + " INTEGER");
}
// Upgrade from version 3 to version 4
if(oldVersion < 4 && newVersion >= 4)
{
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.ICON + " BLOB");
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.EXTRAS + " TEXT");
}
}
public long insertLoyaltyCard(final String store, final String note, final String cardId,
final String barcodeType, final Integer headerColor,
final Integer headerTextColor)
final Integer headerTextColor, final Bitmap icon,
final ExtrasHelper extras) throws JSONException
{
SQLiteDatabase db = getWritableDatabase();
ContentValues contentValues = new ContentValues();
@@ -76,6 +111,8 @@ public class DBHelper extends SQLiteOpenHelper
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType);
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
contentValues.put(LoyaltyCardDbIds.HEADER_TEXT_COLOR, headerTextColor);
contentValues.put(LoyaltyCardDbIds.ICON, icon != null ? convertBitmapToBlob(icon) : null);
contentValues.put(LoyaltyCardDbIds.EXTRAS, extras.toJSON().toString());
final long newId = db.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
return newId;
}
@@ -83,7 +120,8 @@ public class DBHelper extends SQLiteOpenHelper
public boolean insertLoyaltyCard(final SQLiteDatabase db, final int id,
final String store, final String note, final String cardId,
final String barcodeType, final Integer headerColor,
final Integer headerTextColor)
final Integer headerTextColor, final Bitmap icon,
final ExtrasHelper extras) throws JSONException
{
ContentValues contentValues = new ContentValues();
contentValues.put(LoyaltyCardDbIds.ID, id);
@@ -93,6 +131,8 @@ public class DBHelper extends SQLiteOpenHelper
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType);
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
contentValues.put(LoyaltyCardDbIds.HEADER_TEXT_COLOR, headerTextColor);
contentValues.put(LoyaltyCardDbIds.ICON, icon != null ? convertBitmapToBlob(icon) : null);
contentValues.put(LoyaltyCardDbIds.EXTRAS, extras.toJSON().toString());
final long newId = db.insert(LoyaltyCardDbIds.TABLE, null, contentValues);
return (newId != -1);
}
@@ -100,7 +140,8 @@ public class DBHelper extends SQLiteOpenHelper
public boolean updateLoyaltyCard(final int id, final String store, final String note,
final String cardId, final String barcodeType,
final Integer headerColor, final Integer headerTextColor)
final Integer headerColor, final Integer headerTextColor,
final Bitmap icon, final ExtrasHelper extras) throws JSONException
{
SQLiteDatabase db = getWritableDatabase();
ContentValues contentValues = new ContentValues();
@@ -110,6 +151,8 @@ public class DBHelper extends SQLiteOpenHelper
contentValues.put(LoyaltyCardDbIds.BARCODE_TYPE, barcodeType);
contentValues.put(LoyaltyCardDbIds.HEADER_COLOR, headerColor);
contentValues.put(LoyaltyCardDbIds.HEADER_TEXT_COLOR, headerTextColor);
contentValues.put(LoyaltyCardDbIds.ICON, icon != null ? convertBitmapToBlob(icon) : null);
contentValues.put(LoyaltyCardDbIds.EXTRAS, extras.toJSON().toString());
int rowsUpdated = db.update(LoyaltyCardDbIds.TABLE, contentValues,
LoyaltyCardDbIds.ID + "=?",
new String[]{Integer.toString(id)});

View File

@@ -0,0 +1,77 @@
package protect.card_locker;
import androidx.annotation.NonNull;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
public class ExtrasHelper {
private HashMap<String, LinkedHashMap<String, String>> extras = new HashMap<>();
public ExtrasHelper fromJSON(JSONObject json) throws JSONException
{
Iterator<String> languages = json.keys();
while(languages.hasNext())
{
String language = languages.next();
Iterator<String> keys = json.getJSONObject(language).keys();
while(keys.hasNext())
{
String key = keys.next();
addLanguageValue(language, key, json.getJSONObject(language).getString(key));
}
}
return this;
}
public JSONObject toJSON() throws JSONException
{
return new JSONObject(extras);
}
public void addLanguageValue(String language, String key, String value)
{
if(!extras.containsKey(language))
{
extras.put(language, new LinkedHashMap<String, String>());
}
LinkedHashMap<String, String> values = extras.get(language);
values.put(key, value);
extras.put(language, values);
}
@NonNull
public LinkedHashMap<String, String> getAllValues(String language)
{
return getAllValues(new String[]{language});
}
@NonNull
public LinkedHashMap<String, String> getAllValues(String[] languages)
{
LinkedHashMap<String, String> values = new LinkedHashMap<>();
// Get least preferred language (last in list) first
// Then go further and further to the start of the list (preferred language)
for(int i = (languages.length - 1); i >= 0; i--)
{
LinkedHashMap<String, String> languageValues = extras.get(languages[i]);
if(languageValues != null)
{
values.putAll(languageValues);
}
}
return values;
}
}

View File

@@ -2,7 +2,13 @@ package protect.card_locker;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.util.Base64;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.InvalidObjectException;
public class ImportURIHelper {
@@ -12,6 +18,8 @@ public class ImportURIHelper {
private static final String BARCODE_TYPE = DBHelper.LoyaltyCardDbIds.BARCODE_TYPE;
private static final String HEADER_COLOR = DBHelper.LoyaltyCardDbIds.HEADER_COLOR;
private static final String HEADER_TEXT_COLOR = DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR;
private static final String ICON = DBHelper.LoyaltyCardDbIds.ICON;
private static final String EXTRAS = DBHelper.LoyaltyCardDbIds.EXTRAS;
private final Context context;
private final String host;
@@ -24,12 +32,12 @@ public class ImportURIHelper {
this.context = context;
host = context.getResources().getString(R.string.intent_import_card_from_url_host);
path = context.getResources().getString(R.string.intent_import_card_from_url_path_prefix);
oldHost = "brarcher.github.io";
oldPath = "/loyalty-card-locker/share";
oldHost = context.getResources().getString(R.string.intent_import_card_from_url_host_old);
oldPath = context.getResources().getString(R.string.intent_import_card_from_url_path_prefix_old);
shareText = context.getResources().getString(R.string.intent_import_card_from_url_share_text);
}
private boolean isImportUri(Uri uri) {
public boolean isImportUri(Uri uri) {
return (uri.getHost().equals(host) && uri.getPath().equals(path)) || (uri.getHost().equals(oldHost) && uri.getPath().equals(oldPath));
}
@@ -57,14 +65,36 @@ public class ImportURIHelper {
{
headerTextColor = Integer.parseInt(unparsedHeaderTextColor);
}
return new LoyaltyCard(-1, store, note, cardId, barcodeType, headerColor, headerTextColor);
} catch (NullPointerException | NumberFormatException ex) {
String iconData = uri.getQueryParameter(ICON);
Bitmap icon = null;
if(iconData != null && !iconData.isEmpty())
{
byte[] iconBytes = Base64.decode(iconData, Base64.URL_SAFE);
icon = DBHelper.convertBitmapBlobToBitmap(iconBytes);
}
String extrasData = uri.getQueryParameter(EXTRAS);
ExtrasHelper extras = new ExtrasHelper();
if(extrasData != null && !extrasData.isEmpty())
{
extras.fromJSON(new JSONObject(uri.getQueryParameter(EXTRAS)));
}
return new LoyaltyCard(-1, store, note, cardId, barcodeType, headerColor, headerTextColor, icon, extras);
} catch (NullPointerException | NumberFormatException | JSONException ex) {
throw new InvalidObjectException("Not a valid import URI");
}
}
// Protected for usage in tests
protected Uri toUri(LoyaltyCard loyaltyCard) {
protected Uri toUri(LoyaltyCard loyaltyCard) throws JSONException {
String icon = "";
if(loyaltyCard.icon != null)
{
icon = Base64.encodeToString(DBHelper.convertBitmapToBlob(loyaltyCard.icon), Base64.URL_SAFE);
}
Uri.Builder uriBuilder = new Uri.Builder();
uriBuilder.scheme("https");
uriBuilder.authority(host);
@@ -81,6 +111,8 @@ public class ImportURIHelper {
{
uriBuilder.appendQueryParameter(HEADER_TEXT_COLOR, loyaltyCard.headerTextColor.toString());
}
uriBuilder.appendQueryParameter(ICON, icon);
uriBuilder.appendQueryParameter(EXTRAS, loyaltyCard.extras.toJSON().toString());
return uriBuilder.build();
}
@@ -95,7 +127,14 @@ public class ImportURIHelper {
context.startActivity(shareIntent);
}
public void startShareIntent(LoyaltyCard loyaltyCard) {
startShareIntent(toUri(loyaltyCard));
public boolean startShareIntent(LoyaltyCard loyaltyCard) {
try
{
startShareIntent(toUri(loyaltyCard));
return true;
}
catch (JSONException ex) {}
return false;
}
}

View File

@@ -1,8 +1,13 @@
package protect.card_locker;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import androidx.annotation.Nullable;
import org.json.JSONException;
import org.json.JSONObject;
public class LoyaltyCard
{
public final int id;
@@ -17,8 +22,15 @@ public class LoyaltyCard
@Nullable
public final Integer headerTextColor;
@Nullable
public final Bitmap icon;
@Nullable
public final ExtrasHelper extras;
public LoyaltyCard(final int id, final String store, final String note, final String cardId,
final String barcodeType, final Integer headerColor, final Integer headerTextColor)
final String barcodeType, final Integer headerColor, final Integer headerTextColor,
final Bitmap icon, final ExtrasHelper extras)
{
this.id = id;
this.store = store;
@@ -27,6 +39,8 @@ public class LoyaltyCard
this.barcodeType = barcodeType;
this.headerColor = headerColor;
this.headerTextColor = headerTextColor;
this.icon = icon;
this.extras = extras;
}
public static LoyaltyCard toLoyaltyCard(Cursor cursor)
@@ -53,6 +67,32 @@ public class LoyaltyCard
headerTextColor = cursor.getInt(headerTextColorColumn);
}
return new LoyaltyCard(id, store, note, cardId, barcodeType, headerColor, headerTextColor);
int iconColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.ICON);
Bitmap icon = null;
if(cursor.isNull(iconColumn) == false)
{
byte[] iconData = cursor.getBlob(iconColumn);
icon = BitmapFactory.decodeByteArray(iconData, 0, iconData.length);
}
int extrasColumn = cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.EXTRAS);
ExtrasHelper extras = new ExtrasHelper();
if(cursor.isNull(extrasColumn) == false)
{
try
{
extras = extras.fromJSON(new JSONObject(cursor.getString(extrasColumn)));
}
catch (JSONException ex)
{
// That this is actually JSON is an implementation detail
// The important part is that the DB is in a bad state
throw new IllegalArgumentException(ex);
}
}
return new LoyaltyCard(id, store, note, cardId, barcodeType, headerColor, headerTextColor, icon, extras);
}
}

View File

@@ -2,6 +2,7 @@ package protect.card_locker;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -61,10 +62,18 @@ class LoyaltyCardCursorAdapter extends CursorAdapter
int tileLetterFontSize = context.getResources().getDimensionPixelSize(R.dimen.tileLetterFontSize);
int pixelSize = context.getResources().getDimensionPixelSize(R.dimen.cardThumbnailSize);
Integer letterBackgroundColor = loyaltyCard.headerColor;
Integer letterTextColor = loyaltyCard.headerTextColor;
LetterBitmap letterBitmap = new LetterBitmap(context, loyaltyCard.store, loyaltyCard.store,
tileLetterFontSize, pixelSize, pixelSize, letterBackgroundColor, letterTextColor);
thumbnail.setImageBitmap(letterBitmap.getLetterTile());
if(loyaltyCard.icon == null)
{
Integer letterBackgroundColor = loyaltyCard.headerColor;
Integer letterTextColor = loyaltyCard.headerTextColor;
LetterBitmap letterBitmap = new LetterBitmap(context, loyaltyCard.store, loyaltyCard.store,
tileLetterFontSize, pixelSize, pixelSize, letterBackgroundColor, letterTextColor);
thumbnail.setImageBitmap(letterBitmap.getLetterTile());
}
else
{
thumbnail.setImageBitmap(loyaltyCard.icon);
}
}
}

View File

@@ -4,6 +4,7 @@ import android.app.Activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
@@ -32,6 +33,10 @@ import com.google.zxing.integration.android.IntentResult;
import com.jaredrummler.android.colorpicker.ColorPickerDialog;
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InvalidObjectException;
public class LoyaltyCardEditActivity extends AppCompatActivity
@@ -61,23 +66,53 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
int loyaltyCardId;
boolean updateLoyaltyCard;
Uri importLoyaltyCardUri = null;
String importLoyaltyCardType = null;
Integer headingColorValue = null;
Integer headingStoreTextColorValue = null;
Bitmap icon = null;
ExtrasHelper extras = new ExtrasHelper();
DBHelper db;
ImportURIHelper importUriHelper;
PkpassImporter pkpassImporter;
private void extractIntentFields(Intent intent)
{
final Bundle b = intent.getExtras();
loyaltyCardId = b != null ? b.getInt("id") : 0;
updateLoyaltyCard = b != null && b.getBoolean("update", false);
importLoyaltyCardType = intent.getType();
importLoyaltyCardUri = intent.getData();
Log.d(TAG, "View activity: id=" + loyaltyCardId
+ ", updateLoyaltyCard=" + Boolean.toString(updateLoyaltyCard));
}
private LoyaltyCard importCard(String type, Uri uri)
{
// Import URI
if(importUriHelper.isImportUri(uri))
{
try
{
return importUriHelper.parse(importLoyaltyCardUri);
}
catch (InvalidObjectException ex)
{
return null;
}
}
// Pkpass
try
{
return pkpassImporter.fromURI(uri);
}
catch (IOException | JSONException ex) {}
return null;
}
@Override
protected void onCreate(Bundle savedInstanceState)
{
@@ -96,6 +131,7 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
db = new DBHelper(this);
importUriHelper = new ImportURIHelper(this);
pkpassImporter = new PkpassImporter(this);
storeFieldEdit = findViewById(R.id.storeNameEdit);
noteFieldEdit = findViewById(R.id.noteEdit);
@@ -186,15 +222,17 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
}
}
extras = loyaltyCard.extras;
setTitle(R.string.editCardTitle);
}
else if(importLoyaltyCardUri != null)
{
// Try to parse
LoyaltyCard importCard;
try {
importCard = importUriHelper.parse(importLoyaltyCardUri);
} catch (InvalidObjectException ex) {
LoyaltyCard importCard = importCard(importLoyaltyCardType, importLoyaltyCardUri);
if(importCard == null)
{
Toast.makeText(this, R.string.failedParsingImportUriError, Toast.LENGTH_LONG).show();
finish();
return;
@@ -206,6 +244,8 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
barcodeTypeField.setText(importCard.barcodeType);
headingColorValue = importCard.headerColor;
headingStoreTextColorValue = importCard.headerTextColor;
icon = importCard.icon;
extras = importCard.extras;
}
else
{
@@ -331,7 +371,11 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
saveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doSave();
try {
doSave();
} catch (JSONException ex) {
Toast.makeText(getApplicationContext(), R.string.failedSavingCard, Toast.LENGTH_LONG).show();
}
}
});
}
@@ -378,7 +422,7 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
}
}
private void doSave()
private void doSave() throws JSONException
{
String store = storeFieldEdit.getText().toString();
String note = noteFieldEdit.getText().toString();
@@ -406,12 +450,12 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
if(updateLoyaltyCard)
{
db.updateLoyaltyCard(loyaltyCardId, store, note, cardId, barcodeType, headingColorValue, headingStoreTextColorValue);
db.updateLoyaltyCard(loyaltyCardId, store, note, cardId, barcodeType, headingColorValue, headingStoreTextColorValue, icon, extras);
Log.i(TAG, "Updated " + loyaltyCardId + " to " + cardId);
}
else
{
loyaltyCardId = (int)db.insertLoyaltyCard(store, note, cardId, barcodeType, headingColorValue, headingStoreTextColorValue);
loyaltyCardId = (int)db.insertLoyaltyCard(store, note, cardId, barcodeType, headingColorValue, headingStoreTextColorValue, icon, extras);
}
finish();

View File

@@ -1,6 +1,7 @@
package protect.card_locker;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.Color;
@@ -8,6 +9,7 @@ import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import androidx.appcompat.app.AlertDialog;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.graphics.ColorUtils;
import androidx.core.graphics.drawable.DrawableCompat;
@@ -32,6 +34,12 @@ import android.widget.Toast;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.zxing.BarcodeFormat;
import org.json.JSONException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import protect.card_locker.preferences.Settings;
@@ -317,6 +325,13 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
item.setVisible(false);
}
if(loyaltyCard != null && !loyaltyCard.extras.getAllValues(new String[]{Locale.getDefault().getLanguage(), "en", ""}).isEmpty())
{
MenuItem extraItem = menu.findItem(R.id.action_view_extras);
extraItem.setIcon(getIcon(R.drawable.ic_info_outline_white, backgroundNeedsDarkIcons));
extraItem.setVisible(true);
}
menu.findItem(R.id.action_share).setIcon(getIcon(R.drawable.ic_share_white, backgroundNeedsDarkIcons));
return super.onCreateOptionsMenu(menu);
@@ -348,6 +363,18 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
}
rotationEnabled = !rotationEnabled;
return true;
case R.id.action_view_extras:
try
{
displayExtrasDialog();
}
catch (JSONException ex)
{
Toast.makeText(this, R.string.failedShowingExtras, Toast.LENGTH_LONG).show();
finish();
}
return true;
}
return super.onOptionsItemSelected(item);
@@ -370,6 +397,29 @@ public class LoyaltyCardViewActivity extends AppCompatActivity
}
}
private void displayExtrasDialog() throws JSONException
{
StringBuilder items = new StringBuilder();
HashMap<String, String> extraValues = loyaltyCard.extras.getAllValues(new String[]{Locale.getDefault().getLanguage(), "en", ""});
for(Map.Entry<String, String> entry : extraValues.entrySet())
{
items.append(entry.getValue() + "\n");
}
new AlertDialog.Builder(this)
.setMessage(items.toString())
.setCancelable(true)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
}
})
.show();
}
/**
* When enabled, hides the status bar and moves the barcode to the top of the screen.
*

View File

@@ -14,7 +14,6 @@ import android.database.Cursor;
import android.os.Bundle;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import android.util.Log;
@@ -210,7 +209,10 @@ public class MainActivity extends AppCompatActivity
else if(item.getItemId() == R.id.action_share)
{
final ImportURIHelper importURIHelper = new ImportURIHelper(this);
importURIHelper.startShareIntent(card);
if(importURIHelper.startShareIntent(card))
{
Toast.makeText(this, R.string.failedSharingCard, Toast.LENGTH_LONG).show();
}
return true;
}
}

View File

@@ -0,0 +1,319 @@
package protect.card_locker;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.net.Uri;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.zxing.BarcodeFormat;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class PkpassImporter {
private Context context;
private Bitmap icon = null;
private HashMap<String, HashMap<String, String>> translations = new HashMap<>();
public PkpassImporter(Context context)
{
this.context = context;
}
private ByteArrayOutputStream readZipInputStream(ZipInputStream zipInputStream) throws IOException
{
byte[] buffer = new byte[2048];
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int size;
while((size = zipInputStream.read(buffer, 0, buffer.length)) != -1)
{
byteArrayOutputStream.write(buffer, 0, size);
}
return byteArrayOutputStream;
}
private void loadBitmap(ByteArrayOutputStream byteArrayOutputStream)
{
byte[] bytes = byteArrayOutputStream.toByteArray();
// Only keep the largest icon
if(icon != null && bytes.length < icon.getByteCount())
{
return;
}
icon = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
}
// FIXME: Probably very fragile
private void parseTranslations(String language, String iOSTranslationFileContent)
{
if(!translations.containsKey(language))
{
translations.put(language, new HashMap<String, String>());
}
HashMap<String, String> values = translations.get(language);
for (String entry : iOSTranslationFileContent.trim().split(";"))
{
String[] parts = entry.split(" = ", 2);
// Remove all spaces around the key and value
String key = parts[0].trim();
String value = parts[1].trim();
// iOS .string files quote everything in double quotes, we don't need to keep those
values.put(key.substring(1, key.length()-1), value.substring(1, value.length()-1));
}
translations.put(language, values);
}
private ExtrasHelper appendData(ExtrasHelper extrasHelper, JSONObject pkpassJSON, String styleKey, String arrayName) throws JSONException
{
// https://developer.apple.com/library/archive/documentation/UserExperience/Reference/PassKit_Bundle/Chapters/FieldDictionary.html#//apple_ref/doc/uid/TP40012026-CH4-SW1
JSONArray fields;
// These are all optional, so don't throw an exception if they don't exist
try
{
fields = pkpassJSON.getJSONObject(styleKey).getJSONArray(arrayName);
}
catch (JSONException ex)
{
return extrasHelper;
}
for(int i = 0; i < fields.length(); i++)
{
JSONObject fieldObject = fields.getJSONObject(i);
String key = fieldObject.getString("key");
String label = fieldObject.getString("label");
String value = fieldObject.getString("value");
// Label is optional
if(label == null)
{
label = key;
}
// Add the completely untranslated stuff as fallback
String formattedUntranslatedValue = value;
if(!label.isEmpty())
{
formattedUntranslatedValue = label + ": " + value;
}
extrasHelper.addLanguageValue("", key, formattedUntranslatedValue);
// Try to find translations
for(Map.Entry<String, HashMap<String, String>> language : translations.entrySet())
{
String translatedLabel = label;
if(language.getValue().containsKey(label))
{
translatedLabel = language.getValue().get(label);
}
String translatedValue = value;
if(language.getValue().containsKey(value))
{
translatedValue = language.getValue().get(value);
}
String formattedValue = translatedValue;
if(!translatedLabel.isEmpty())
{
formattedValue = translatedLabel + ": " + translatedValue;
}
extrasHelper.addLanguageValue(language.getKey(), key, formattedValue);
}
}
return extrasHelper;
}
public LoyaltyCard fromURI(Uri uri) throws IOException, JSONException {
return fromInputStream(context.getContentResolver().openInputStream(uri));
}
public LoyaltyCard fromInputStream(InputStream inputStream) throws IOException, JSONException
{
String passJSONString = null;
ZipInputStream zipInputStream = new ZipInputStream(inputStream);
ZipEntry entry;
// We first want to parse the translations
while((entry = zipInputStream.getNextEntry()) != null) {
if(entry.getName().endsWith("pass.strings"))
{
// Example: en.lproj/pass.strings
String language = entry.getName().substring(0, entry.getName().indexOf("."));
parseTranslations(language, readZipInputStream(zipInputStream).toString("UTF-8"));
}
else if(entry.getName().equals("pass.json"))
{
passJSONString = readZipInputStream(zipInputStream).toString("UTF-8");
}
else if(entry.getName().equals("icon.png") || entry.getName().equals("icon@2x.png"))
{
loadBitmap(readZipInputStream(zipInputStream));
}
}
if(passJSONString == null)
{
return null;
}
return fromPassJSON(new JSONObject(passJSONString));
}
public LoyaltyCard fromPassJSON(JSONObject json) throws JSONException
{
String store = json.getString("organizationName");
String note = json.getString("description");
// https://developer.apple.com/library/archive/documentation/UserExperience/Reference/PassKit_Bundle/Chapters/TopLevel.html#//apple_ref/doc/uid/TP40012026-CH2-SW1
// barcodes is the new field
// barcode is deprecated, but used on old iOS versions, so we do fall back to it
JSONObject barcode = null;
JSONArray barcodes = null;
try {
barcodes = json.getJSONArray("barcodes");
} catch (JSONException ex) {
}
if (barcodes != null) {
barcode = barcodes.getJSONObject(0);
} else {
barcode = json.getJSONObject("barcode");
}
if (barcode == null) {
return null;
}
String cardId = barcode.getString("message");
// https://developer.apple.com/library/archive/documentation/UserExperience/Reference/PassKit_Bundle/Chapters/LowerLevel.html#//apple_ref/doc/uid/TP40012026-CH3-SW3
// Required. Barcode format. For the barcode dictionary, you can use only the following values: PKBarcodeFormatQR, PKBarcodeFormatPDF417, or PKBarcodeFormatAztec. For dictionaries in the barcodes array, you may also use PKBarcodeFormatCode128.
ImmutableMap<String, String> supportedBarcodeTypes = ImmutableMap.<String, String>builder()
.put("PKBarcodeFormatQR", BarcodeFormat.QR_CODE.name())
.put("PKBarcodeFormatPDF417", BarcodeFormat.PDF_417.name())
.put("PKBarcodeFormatAztec", BarcodeFormat.AZTEC.name())
.put("PKBarcodeFormatCode128", BarcodeFormat.CODE_128.name())
.build();
String barcodeType = supportedBarcodeTypes.get(barcode.getString("format"));
if(barcodeType == null)
{
return null;
}
// Prepare to parse colors
Pattern rgbPattern = Pattern.compile("^rgb\\(\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*\\)$");
// Optional. Background color of the pass, specified as an CSS-style RGB triple. For example, rgb(23, 187, 82).
Integer headerColor = null;
Matcher headerColorMatcher = rgbPattern.matcher(json.getString("backgroundColor"));
if(headerColorMatcher.find())
{
headerColor = Color.rgb(
Integer.parseInt(headerColorMatcher.group(1)),
Integer.parseInt(headerColorMatcher.group(2)),
Integer.parseInt(headerColorMatcher.group(3)));
}
if(headerColor == null)
{
// Maybe they violate the spec, let's parse it in a format Android understands
// Necessary for at least Eurowings
try
{
headerColor = Color.parseColor(json.getString("backgroundColor"));
}
catch (IllegalArgumentException ex) {}
}
// Optional. Color of the label text, specified as a CSS-style RGB triple. For example, rgb(255, 255, 255).
Integer headerTextColor = null;
Matcher headerTextColorMatcher = rgbPattern.matcher(json.getString("labelColor"));
if(headerTextColorMatcher.find())
{
headerTextColor = Color.rgb(
Integer.parseInt(headerTextColorMatcher.group(1)),
Integer.parseInt(headerTextColorMatcher.group(2)),
Integer.parseInt(headerTextColorMatcher.group(3)));
}
if(headerTextColor == null)
{
// Maybe they violate the spec, let's parse it in a format Android understands
// Necessary for at least Eurowings
try
{
headerTextColor = Color.parseColor(json.getString("labelColor"));
}
catch (IllegalArgumentException ex) {}
}
// https://developer.apple.com/library/archive/documentation/UserExperience/Reference/PassKit_Bundle/Chapters/TopLevel.html#//apple_ref/doc/uid/TP40012026-CH2-SW6
// There needs to be exactly one style key
String styleKey = null;
ImmutableList<String> possibleStyleKeys = ImmutableList.<String>builder()
.add("boardingPass")
.add("coupon")
.add("eventTicket")
.add("generic")
.add("storeCard")
.build();
for(int i = 0; i < possibleStyleKeys.size(); i++)
{
String possibleStyleKey = possibleStyleKeys.get(i);
if(json.has(possibleStyleKey))
{
styleKey = possibleStyleKey;
break;
}
}
if(styleKey == null)
{
return null;
}
// https://developer.apple.com/library/archive/documentation/UserExperience/Reference/PassKit_Bundle/Chapters/LowerLevel.html#//apple_ref/doc/uid/TP40012026-CH3-SW14
ExtrasHelper extras = new ExtrasHelper();
extras = appendData(extras, json, styleKey, "headerFields");
extras = appendData(extras, json, styleKey, "primaryFields");
extras = appendData(extras, json, styleKey, "secondaryFields");
extras = appendData(extras, json, styleKey, "auxiliaryFields");
extras = appendData(extras, json, styleKey, "backFields");
return new LoyaltyCard(-1, store, note, cardId, barcodeType, headerColor, headerTextColor, icon, extras);
}
}

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 742 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -12,4 +12,10 @@
android:icon="@drawable/ic_share_white"
android:title="@string/share"
app:showAsAction="always"/>
<item
android:id="@+id/action_view_extras"
android:icon="@drawable/ic_info_outline_white"
android:title="@string/moreInfo"
app:showAsAction="always"
android:visible="false"/>
</menu>

View File

@@ -20,6 +20,7 @@
<string name="enterCard">Enter Card</string>
<string name="editCard">Edit Card</string>
<string name="edit">Edit</string>
<string name="moreInfo">More Info</string>
<string name="delete">Delete</string>
<string name="confirm">Confirm</string>
<string name="lockScreen">Block Rotation</string>
@@ -42,6 +43,9 @@
<string name="noStoreError">No Store entered</string>
<string name="noCardIdError">No Card ID entered</string>
<string name="noCardExistsError">Could not lookup loyalty card</string>
<string name="failedSharingCard">Could not share card</string>
<string name="failedShowingExtras">Could not show extra information: data not correctly formatted</string>
<string name="failedSavingCard">Could not save card</string>
<string name="failedParsingImportUriError">Could not parse the import URI</string>
<string name="cardIdFormat">%1$s: %2$s</string>
<string name="importExport">Import/Export</string>

View File

@@ -2,12 +2,17 @@ package protect.card_locker;
import android.app.Activity;
import android.content.ContentValues;
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 com.google.zxing.BarcodeFormat;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -28,19 +33,24 @@ public class DatabaseTest
private static final Integer DEFAULT_HEADER_COLOR = Color.BLACK;
private static final Integer DEFAULT_HEADER_TEXT_COLOR = Color.WHITE;
private static Bitmap DEFAULT_ICON = BitmapFactory.decodeResource(Resources.getSystem(), R.mipmap.ic_launcher);
private static ExtrasHelper DEFAULT_EXTRAS;
@Before
public void setUp()
public void setUp() throws JSONException
{
DEFAULT_EXTRAS = new ExtrasHelper();
DEFAULT_EXTRAS.addLanguageValue("en", "key", "value");
Activity activity = Robolectric.setupActivity(MainActivity.class);
db = new DBHelper(activity);
}
@Test
public void addRemoveOneGiftCard()
public void addRemoveOneGiftCard() throws JSONException
{
assertEquals(0, db.getLoyaltyCardCount());
long id = db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR, DEFAULT_HEADER_TEXT_COLOR);
long id = db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR, DEFAULT_HEADER_TEXT_COLOR, DEFAULT_ICON, DEFAULT_EXTRAS);
boolean result = (id != -1);
assertTrue(result);
assertEquals(1, db.getLoyaltyCardCount());
@@ -59,14 +69,14 @@ public class DatabaseTest
}
@Test
public void updateGiftCard()
public void updateGiftCard() throws JSONException
{
long id = db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR, DEFAULT_HEADER_TEXT_COLOR);
long id = db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR, DEFAULT_HEADER_TEXT_COLOR, DEFAULT_ICON, DEFAULT_EXTRAS);
boolean result = (id != -1);
assertTrue(result);
assertEquals(1, db.getLoyaltyCardCount());
result = db.updateLoyaltyCard(1, "store1", "note1", "cardId1", BarcodeFormat.AZTEC.toString(), DEFAULT_HEADER_COLOR, DEFAULT_HEADER_TEXT_COLOR);
result = db.updateLoyaltyCard(1, "store1", "note1", "cardId1", BarcodeFormat.AZTEC.toString(), DEFAULT_HEADER_COLOR, DEFAULT_HEADER_TEXT_COLOR, DEFAULT_ICON, DEFAULT_EXTRAS);
assertTrue(result);
assertEquals(1, db.getLoyaltyCardCount());
@@ -76,23 +86,24 @@ public class DatabaseTest
assertEquals("note1", loyaltyCard.note);
assertEquals("cardId1", loyaltyCard.cardId);
assertEquals(BarcodeFormat.AZTEC.toString(), loyaltyCard.barcodeType);
assertEquals(DEFAULT_EXTRAS.toJSON().toString(), loyaltyCard.extras.toJSON().toString());
}
@Test
public void updateMissingGiftCard()
public void updateMissingGiftCard() throws JSONException
{
assertEquals(0, db.getLoyaltyCardCount());
boolean result = db.updateLoyaltyCard(1, "store1", "note1", "cardId1",
BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR, DEFAULT_HEADER_TEXT_COLOR);
BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR, DEFAULT_HEADER_TEXT_COLOR, DEFAULT_ICON, DEFAULT_EXTRAS);
assertEquals(false, result);
assertEquals(0, db.getLoyaltyCardCount());
}
@Test
public void emptyGiftCardValues()
public void emptyGiftCardValues() throws JSONException
{
long id = db.insertLoyaltyCard("", "", "", "", null, null);
long id = db.insertLoyaltyCard("", "", "", "", null, null, null, new ExtrasHelper());
boolean result = (id != -1);
assertTrue(result);
assertEquals(1, db.getLoyaltyCardCount());
@@ -103,10 +114,11 @@ public class DatabaseTest
assertEquals("", loyaltyCard.note);
assertEquals("", loyaltyCard.cardId);
assertEquals("", loyaltyCard.barcodeType);
assertEquals("{}", loyaltyCard.extras.toJSON().toString());
}
@Test
public void giftCardsViaCursor()
public void giftCardsViaCursor() throws JSONException
{
final int CARDS_TO_ADD = 10;
@@ -115,7 +127,7 @@ public class DatabaseTest
for(int index = CARDS_TO_ADD-1; index >= 0; index--)
{
long id = db.insertLoyaltyCard("store" + index, "note" + index, "cardId" + index,
BarcodeFormat.UPC_A.toString(), index, index*2);
BarcodeFormat.UPC_A.toString(), index, index*2, DEFAULT_ICON, DEFAULT_EXTRAS);
boolean result = (id != -1);
assertTrue(result);
}
@@ -138,6 +150,7 @@ public class DatabaseTest
assertEquals(BarcodeFormat.UPC_A.toString(), cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.BARCODE_TYPE)));
assertEquals(index, cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_COLOR)));
assertEquals(index*2, cursor.getInt(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.HEADER_TEXT_COLOR)));
assertEquals("{\"en\":{\"key\":\"value\"}}", cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbIds.EXTRAS)));
cursor.moveToNext();
}
@@ -172,7 +185,7 @@ public class DatabaseTest
}
@Test
public void databaseUpgradeFromVersion1()
public void databaseUpgradeFromVersion1() throws JSONException
{
SQLiteDatabase database = db.getWritableDatabase();
@@ -193,6 +206,7 @@ public class DatabaseTest
assertEquals("", card.note);
assertEquals(null, card.headerColor);
assertEquals(null, card.headerTextColor);
assertEquals("{}", card.extras.toJSON().toString());
database.close();
}

View File

@@ -9,6 +9,8 @@ import android.os.Environment;
import com.google.zxing.BarcodeFormat;
import org.apache.tools.ant.filters.StringInputStream;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -46,10 +48,14 @@ public class ImportExportTest
private final String BARCODE_DATA = "428311627547";
private final String BARCODE_TYPE = BarcodeFormat.UPC_A.name();
private ExtrasHelper EXTRAS;
@Before
public void setUp()
public void setUp() throws JSONException
{
EXTRAS = new ExtrasHelper();
EXTRAS.addLanguageValue("en", "key", "value");
activity = Robolectric.setupActivity(MainActivity.class);
db = new DBHelper(activity);
nowMs = System.currentTimeMillis();
@@ -64,14 +70,14 @@ public class ImportExportTest
* an index in the store name.
* @param cardsToAdd
*/
private void addLoyaltyCards(int cardsToAdd)
private void addLoyaltyCards(int cardsToAdd) throws JSONException
{
// Add in reverse order to test sorting
for(int index = cardsToAdd; index > 0; index--)
{
String storeName = String.format("store, \"%4d", index);
String note = String.format("note, \"%4d", index);
long id = db.insertLoyaltyCard(storeName, note, BARCODE_DATA, BARCODE_TYPE, index, index*2);
long id = db.insertLoyaltyCard(storeName, note, BARCODE_DATA, BARCODE_TYPE, index, index*2, null, EXTRAS);
boolean result = (id != -1);
assertTrue(result);
}
@@ -84,7 +90,7 @@ public class ImportExportTest
* specified in addLoyaltyCards(), and are in sequential order
* where the smallest card's index is 1
*/
private void checkLoyaltyCards()
private void checkLoyaltyCards() throws JSONException
{
Cursor cursor = db.getLoyaltyCardCursor();
int index = 1;
@@ -102,6 +108,7 @@ public class ImportExportTest
assertEquals(BARCODE_TYPE, card.barcodeType);
assertEquals(Integer.valueOf(index), card.headerColor);
assertEquals(Integer.valueOf(index*2), card.headerTextColor);
assertEquals("{\"en\":{\"key\":\"value\"}}", card.extras.toJSON().toString());
index++;
}
@@ -121,7 +128,7 @@ public class ImportExportTest
}
@Test
public void multipleCardsExportImport() throws IOException
public void multipleCardsExportImport() throws IOException, JSONException
{
final int NUM_CARDS = 10;
@@ -156,7 +163,7 @@ public class ImportExportTest
}
@Test
public void importExistingCardsNotReplace() throws IOException
public void importExistingCardsNotReplace() throws IOException, JSONException
{
final int NUM_CARDS = 10;
@@ -189,7 +196,7 @@ public class ImportExportTest
}
@Test
public void corruptedImportNothingSaved() throws IOException
public void corruptedImportNothingSaved() throws IOException, JSONException
{
final int NUM_CARDS = 10;
@@ -234,7 +241,7 @@ public class ImportExportTest
}
@Test
public void useImportExportTask() throws FileNotFoundException
public void useImportExportTask() throws FileNotFoundException, JSONException
{
final int NUM_CARDS = 10;

View File

@@ -1,17 +1,25 @@
package protect.card_locker;
import android.app.Activity;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.net.Uri;
import com.google.zxing.BarcodeFormat;
import org.json.JSONException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.io.InvalidObjectException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static protect.card_locker.DBHelper.LoyaltyCardDbIds;
@@ -31,13 +39,18 @@ public class ImportURITest {
}
@Test
public void ensureNoDataLoss() throws InvalidObjectException
public void ensureNoDataLoss() throws InvalidObjectException, JSONException
{
// Generate card
db.insertLoyaltyCard("store", "note", BarcodeFormat.UPC_A.toString(), LoyaltyCardDbIds.BARCODE_TYPE, Color.BLACK, Color.WHITE);
Bitmap icon = BitmapFactory.decodeResource(Resources.getSystem(), R.mipmap.ic_launcher);
assertNotNull(icon);
ExtrasHelper extrasHelper = new ExtrasHelper();
extrasHelper.addLanguageValue("en", "key", "value");
db.insertLoyaltyCard("store", "note", BarcodeFormat.UPC_A.toString(), LoyaltyCardDbIds.BARCODE_TYPE, Color.BLACK, Color.WHITE, icon, extrasHelper);
// Get card
LoyaltyCard card = db.getLoyaltyCard(1);
assertNotNull(card.icon);
// Card to URI
Uri cardUri = importURIHelper.toUri(card);
@@ -52,13 +65,23 @@ public class ImportURITest {
assertEquals(card.headerTextColor, parsedCard.headerTextColor);
assertEquals(card.note, parsedCard.note);
assertEquals(card.store, parsedCard.store);
assertEquals(card.icon.getWidth(), parsedCard.icon.getWidth());
assertEquals(card.icon.getHeight(), parsedCard.icon.getHeight());
for(int i = 0; i < card.icon.getWidth(); i++)
{
for(int j = 0; j < card.icon.getHeight(); j++)
{
assertEquals(card.icon.getPixel(i, j), parsedCard.icon.getPixel(i, j));
}
}
assertEquals(card.extras.toJSON().toString(), parsedCard.extras.toJSON().toString());
}
@Test
public void ensureNoCrashOnMissingHeaderFields() throws InvalidObjectException
public void ensureNoCrashOnMissingHeaderFields() throws InvalidObjectException, JSONException
{
// Generate card
db.insertLoyaltyCard("store", "note", BarcodeFormat.UPC_A.toString(), LoyaltyCardDbIds.BARCODE_TYPE, null, null);
db.insertLoyaltyCard("store", "note", BarcodeFormat.UPC_A.toString(), LoyaltyCardDbIds.BARCODE_TYPE, null, null, null, new ExtrasHelper());
// Get card
LoyaltyCard card = db.getLoyaltyCard(1);

View File

@@ -10,6 +10,8 @@ import android.widget.TextView;
import com.google.zxing.BarcodeFormat;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -81,9 +83,9 @@ public class LoyaltyCardCursorAdapterTest
@Test
public void TestCursorAdapterEmptyNote()
public void TestCursorAdapterEmptyNote() throws JSONException
{
db.insertLoyaltyCard("store", "", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, Color.WHITE);
db.insertLoyaltyCard("store", "", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, Color.WHITE, null, new ExtrasHelper());
LoyaltyCard card = db.getLoyaltyCard(1);
Cursor cursor = db.getLoyaltyCardCursor();
@@ -95,9 +97,9 @@ public class LoyaltyCardCursorAdapterTest
}
@Test
public void TestCursorAdapterWithNote()
public void TestCursorAdapterWithNote() throws JSONException
{
db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, Color.WHITE);
db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, Color.WHITE, null, new ExtrasHelper());
LoyaltyCard card = db.getLoyaltyCard(1);
Cursor cursor = db.getLoyaltyCardCursor();
@@ -109,9 +111,9 @@ public class LoyaltyCardCursorAdapterTest
}
@Test
public void TestCursorAdapterFontSizes()
public void TestCursorAdapterFontSizes() throws JSONException
{
db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, Color.WHITE);
db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, Color.WHITE, null, new ExtrasHelper());
LoyaltyCard card = db.getLoyaltyCard(1);
Cursor cursor = db.getLoyaltyCardCursor();

View File

@@ -29,6 +29,7 @@ import android.widget.TextView;
import androidx.core.widget.TextViewCompat;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.client.android.Intents;
import org.json.JSONException;
import java.io.IOException;
import org.junit.Before;
import org.junit.Test;
@@ -394,13 +395,13 @@ public class LoyaltyCardViewActivityTest
}
@Test
public void startWithLoyaltyCardEditModeCheckDisplay() throws IOException
public void startWithLoyaltyCardEditModeCheckDisplay() throws IOException, JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(true);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE, null, new ExtrasHelper());
activityController.start();
activityController.visible();
@@ -410,13 +411,13 @@ public class LoyaltyCardViewActivityTest
}
@Test
public void startWithLoyaltyCardViewModeCheckDisplay() throws IOException
public void startWithLoyaltyCardViewModeCheckDisplay() throws IOException, JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(false);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE, null, new ExtrasHelper());
activityController.start();
activityController.visible();
@@ -426,13 +427,13 @@ public class LoyaltyCardViewActivityTest
}
@Test
public void startWithLoyaltyCardWithBarcodeUpdateBarcode() throws IOException
public void startWithLoyaltyCardWithBarcodeUpdateBarcode() throws IOException, JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(true);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", EAN_BARCODE_DATA, EAN_BARCODE_TYPE, Color.BLACK, Color.WHITE);
db.insertLoyaltyCard("store", "note", EAN_BARCODE_DATA, EAN_BARCODE_TYPE, Color.BLACK, Color.WHITE, null, new ExtrasHelper());
activityController.start();
activityController.visible();
@@ -447,13 +448,13 @@ public class LoyaltyCardViewActivityTest
}
@Test
public void startWithLoyaltyCardWithReceiptUpdateReceiptCancel() throws IOException
public void startWithLoyaltyCardWithReceiptUpdateReceiptCancel() throws IOException, JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(true);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", EAN_BARCODE_DATA, EAN_BARCODE_TYPE, Color.BLACK, Color.WHITE);
db.insertLoyaltyCard("store", "note", EAN_BARCODE_DATA, EAN_BARCODE_TYPE, Color.BLACK, Color.WHITE, null, new ExtrasHelper());
activityController.start();
activityController.visible();
@@ -473,13 +474,13 @@ public class LoyaltyCardViewActivityTest
}
@Test
public void checkMenu() throws IOException
public void checkMenu() throws IOException, JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(false);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE, null, new ExtrasHelper());
activityController.start();
activityController.visible();
@@ -488,11 +489,12 @@ public class LoyaltyCardViewActivityTest
final Menu menu = shadowOf(activity).getOptionsMenu();
assertTrue(menu != null);
// The share and settings button should be present
assertEquals(menu.size(), 2);
// The rotation,share and info button should be present
assertEquals(menu.size(), 3);
assertEquals("Block Rotation", menu.findItem(R.id.action_lock_unlock).getTitle().toString());
assertEquals("Share", menu.findItem(R.id.action_share).getTitle().toString());
assertEquals("More Info", menu.findItem(R.id.action_view_extras).getTitle().toString());
}
@Test
@@ -516,13 +518,13 @@ public class LoyaltyCardViewActivityTest
}
@Test
public void startWithoutParametersViewBack()
public void startWithoutParametersViewBack() throws JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(false);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE, null, new ExtrasHelper());
activityController.start();
activityController.visible();
@@ -534,13 +536,13 @@ public class LoyaltyCardViewActivityTest
}
@Test
public void startWithoutColors()
public void startWithoutColors() throws JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(false);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, null, null);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, null, null, null, new ExtrasHelper());
activityController.start();
activityController.visible();
@@ -552,13 +554,13 @@ public class LoyaltyCardViewActivityTest
}
@Test
public void startLoyaltyCardWithoutColorsSave() throws IOException
public void startLoyaltyCardWithoutColorsSave() throws IOException, JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(true);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, null, null);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, null, null, null, new ExtrasHelper());
activityController.start();
activityController.visible();
@@ -569,13 +571,13 @@ public class LoyaltyCardViewActivityTest
}
@Test
public void startLoyaltyCardWithExplicitNoBarcodeSave() throws IOException
public void startLoyaltyCardWithExplicitNoBarcodeSave() throws IOException, JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(true);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, "", Color.BLACK, Color.WHITE);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, "", Color.BLACK, Color.WHITE, null, new ExtrasHelper());
activityController.start();
activityController.visible();
@@ -586,13 +588,13 @@ public class LoyaltyCardViewActivityTest
}
@Test
public void removeBarcodeFromLoyaltyCard() throws IOException
public void removeBarcodeFromLoyaltyCard() throws IOException, JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(true);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE, null, new ExtrasHelper());
activityController.start();
activityController.visible();
@@ -613,13 +615,13 @@ public class LoyaltyCardViewActivityTest
}
@Test
public void startCheckFontSizes()
public void startCheckFontSizes() throws JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(false);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE, null, new ExtrasHelper());
final int STORE_FONT_SIZE = 50;
final int CARD_FONT_SIZE = 40;
@@ -653,7 +655,7 @@ public class LoyaltyCardViewActivityTest
}
@Test
public void checkScreenOrientationLockSetting()
public void checkScreenOrientationLockSetting() throws JSONException
{
for(boolean locked : new boolean[] {false, true})
{
@@ -661,7 +663,7 @@ public class LoyaltyCardViewActivityTest
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE, null, new ExtrasHelper());
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(activity);
settings.edit()
@@ -690,13 +692,54 @@ public class LoyaltyCardViewActivityTest
}
@Test
public void checkBarcodeFullscreenWorkflow()
public void checkMoreInfoNoExtras() throws JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(false);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE, null, new ExtrasHelper());
activityController.start();
activityController.resume();
activityController.visible();
assertEquals(false, activity.isFinishing());
MenuItem item = shadowOf(activity).getOptionsMenu().findItem(R.id.action_view_extras);
assertEquals(false, item.isVisible());
}
@Test
public void checkMoreInfoExtras() throws JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(false);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
ExtrasHelper extrasHelper = new ExtrasHelper();
extrasHelper.addLanguageValue("en", "key", "value");
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE, null, extrasHelper);
activityController.start();
activityController.resume();
activityController.visible();
assertEquals(false, activity.isFinishing());
MenuItem item = shadowOf(activity).getOptionsMenu().findItem(R.id.action_view_extras);
assertEquals(true, item.isVisible());
}
@Test
public void checkBarcodeFullscreenWorkflow() throws JSONException
{
ActivityController activityController = createActivityWithLoyaltyCard(false);
Activity activity = (Activity)activityController.get();
DBHelper db = new DBHelper(activity);
db.insertLoyaltyCard("store", "note", BARCODE_DATA, BARCODE_TYPE, Color.BLACK, Color.WHITE, null, new ExtrasHelper());
activityController.start();
activityController.visible();
@@ -755,7 +798,7 @@ public class LoyaltyCardViewActivityTest
@Test
public void importCard()
{
Uri importUri = Uri.parse("https://thelastproject.github.io/Catima/share?store=Example%20Store&note=&cardid=123456&barcodetype=AZTEC&headercolor=-416706&headertextcolor=-1");
Uri importUri = Uri.parse("https://thelastproject.github.io/Catima/share?store=Example%20Store&note=&cardid=123456&barcodetype=AZTEC&headercolor=-416706&headertextcolor=-1&icon=&extras={}");
Intent intent = new Intent();
intent.setData(importUri);
@@ -776,7 +819,7 @@ public class LoyaltyCardViewActivityTest
@Test
public void importCardOldURL()
{
Uri importUri = Uri.parse("https://brarcher.github.io/loyalty-card-locker/share?store=Example%20Store&note=&cardid=123456&barcodetype=AZTEC&headercolor=-416706&headertextcolor=-1");
Uri importUri = Uri.parse("https://thelastproject.github.io/Catima/share?store=Example%20Store&note=&cardid=123456&barcodetype=AZTEC&headercolor=-416706&headertextcolor=-1");
Intent intent = new Intent();
intent.setData(importUri);

View File

@@ -8,7 +8,6 @@ import android.content.SharedPreferences;
import android.database.Cursor;
import android.graphics.Color;
import android.os.Bundle;
import androidx.appcompat.widget.SearchView;
import android.view.Menu;
import android.view.View;
import android.widget.ListView;
@@ -16,6 +15,7 @@ import android.widget.TextView;
import com.google.zxing.BarcodeFormat;
import org.json.JSONException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -83,7 +83,7 @@ public class MainActivityTest
}
@Test
public void addOneLoyaltyCard()
public void addOneLoyaltyCard() throws JSONException
{
ActivityController activityController = Robolectric.buildActivity(MainActivity.class).create();
@@ -98,7 +98,7 @@ public class MainActivityTest
assertEquals(0, list.getCount());
DBHelper db = new DBHelper(mainActivity);
db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, Color.WHITE);
db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, Color.WHITE, null, new ExtrasHelper());
assertEquals(View.VISIBLE, helpText.getVisibility());
assertEquals(View.GONE, noMatchingCardsText.getVisibility());
@@ -117,7 +117,7 @@ public class MainActivityTest
}
@Test
public void testFiltering()
public void testFiltering() throws JSONException
{
ActivityController activityController = Robolectric.buildActivity(MainActivity.class).create();
@@ -130,8 +130,8 @@ public class MainActivityTest
ListView list = mainActivity.findViewById(R.id.list);
DBHelper db = new DBHelper(mainActivity);
db.insertLoyaltyCard("The First Store", "Initial note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, Color.WHITE);
db.insertLoyaltyCard("The Second Store", "Secondary note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, Color.WHITE);
db.insertLoyaltyCard("The First Store", "Initial note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, Color.WHITE, null, new ExtrasHelper());
db.insertLoyaltyCard("The Second Store", "Secondary note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, Color.WHITE, null, new ExtrasHelper());
activityController.pause();
activityController.resume();

View File

@@ -0,0 +1,384 @@
package protect.card_locker;
import android.app.Activity;
import android.graphics.Color;
import android.net.Uri;
import com.google.zxing.BarcodeFormat;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowContentResolver;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Iterator;
import java.util.Map;
import static org.junit.Assert.assertEquals;
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 23)
public class PkpassTest {
private PkpassImporter pkpassImporter;
@Before
public void setUp()
{
Activity activity = Robolectric.setupActivity(MainActivity.class);
pkpassImporter = new PkpassImporter(activity);
}
@Test
public void parseGenericExample() throws IOException, JSONException
{
// https://github.com/keefmoon/Passbook-Example-Code/blob/master/Pass-Example-Generic/Pass-Example-Generic.pkpass
InputStream inputStream = getClass().getResourceAsStream("Pass-Example-Generic.pkpass");
LoyaltyCard card = pkpassImporter.fromInputStream(inputStream);
// Compare everything
assertEquals(BarcodeFormat.QR_CODE.name(), card.barcodeType);
assertEquals("0000001", card.cardId);
assertEquals("Staff Pass for Employee Number 001", card.note);
assertEquals("Passbook Example Company", card.store);
assertEquals(String.valueOf(Color.rgb(90, 90, 90)), card.headerColor.toString());
assertEquals(String.valueOf(Color.rgb(255, 255, 255)), card.headerTextColor.toString());
// Check if all the extras got parsed correctly
ExtrasHelper extras = card.extras;
// Expecting no English entries
assertEquals(0, extras.getAllValues("en").keySet().size());
// Expecting 7 untranslated entries
assertEquals(7, extras.getAllValues("").keySet().size());
// Untranslated and English + untranslated fallback should return the same
assertEquals(extras.getAllValues(""), extras.getAllValues(new String[]{"en", ""}));
// Check all 7 values
Iterator<Map.Entry<String, String>> extrasKeys = extras.getAllValues("").entrySet().iterator();
// 1
Map.Entry<String, String> entry = extrasKeys.next();
assertEquals("staffNumber", entry.getKey());
assertEquals("Staff Number: 001", entry.getValue());
// 2
entry = extrasKeys.next();
assertEquals("staffName", entry.getKey());
assertEquals("Name: Peter Brooke", entry.getValue());
// 3
entry = extrasKeys.next();
assertEquals("telephoneExt", entry.getKey());
assertEquals("Extension: 9779", entry.getValue());
// 4
entry = extrasKeys.next();
assertEquals("jobTitle", entry.getKey());
assertEquals("Job Title: Chief Pass Creator", entry.getValue());
// 5
entry = extrasKeys.next();
assertEquals("expiryDate", entry.getKey());
assertEquals("Expiry Date: 2013-12-31T00:00-23:59", entry.getValue());
// 6
entry = extrasKeys.next();
assertEquals("managersName", entry.getKey());
assertEquals("Manager's Name: Paul Bailey", entry.getValue());
// 7
entry = extrasKeys.next();
assertEquals("managersExt", entry.getKey());
assertEquals("Manager's Extension: 9673", entry.getValue());
}
@Test
public void parseEurowingsTicket() throws IOException, JSONException
{
// https://github.com/brarcher/loyalty-card-locker/issues/309#issuecomment-563465333
InputStream inputStream = getClass().getResourceAsStream("Eurowings.pkpass");
LoyaltyCard card = pkpassImporter.fromInputStream(inputStream);
// Compare everything
assertEquals(BarcodeFormat.AZTEC.name(), card.barcodeType);
assertEquals("M1DOE/JOHN JBZPPP CGNDBVEW 0954 251A016E0073 148>5181W 9250BEW 00000000000002A0000000000000 0 N", card.cardId);
assertEquals("Eurowings Boarding Pass", card.note);
assertEquals("EUROWINGS", card.store);
// Violates the spec, but we want to support it anyway...
assertEquals(String.valueOf(Color.parseColor("#FFFFFF")), card.headerColor.toString());
assertEquals(String.valueOf(Color.parseColor("#AA0061")), card.headerTextColor.toString());
// Check if all the extras got parsed correctly
ExtrasHelper extras = card.extras;
// Expect 18 English values
assertEquals(18, extras.getAllValues("en").size());
// Expect 18 German values
assertEquals(18, extras.getAllValues("de").size());
// Expect 18 untranslated values
assertEquals(18, extras.getAllValues("").size());
// Expect no French values
assertEquals(0, extras.getAllValues("fr").size());
// Check all 18 English, German and untranslated values
Iterator<Map.Entry<String, String>> englishKeys = extras.getAllValues("en").entrySet().iterator();
Iterator<Map.Entry<String, String>> germanKeys = extras.getAllValues("de").entrySet().iterator();
Iterator<Map.Entry<String, String>> untranslatedKeys = extras.getAllValues("").entrySet().iterator();
// 1
Map.Entry<String, String> englishEntry = englishKeys.next();
Map.Entry<String, String> germanEntry = germanKeys.next();
Map.Entry<String, String> untranslatedEntry = untranslatedKeys.next();
assertEquals("gate", englishEntry.getKey());
assertEquals("gate", germanEntry.getKey());
assertEquals("gate", untranslatedEntry.getKey());
assertEquals("Gate: B61", englishEntry.getValue());
assertEquals("Gate: B61", germanEntry.getValue());
assertEquals("gate_str: B61", untranslatedEntry.getValue());
// 2
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("seat", englishEntry.getKey());
assertEquals("seat", germanEntry.getKey());
assertEquals("seat", untranslatedEntry.getKey());
assertEquals("Seat: 16E", englishEntry.getValue());
assertEquals("Sitz: 16E", germanEntry.getValue());
assertEquals("seat_str: 16E", untranslatedEntry.getValue());
// 3
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("origin", englishEntry.getKey());
assertEquals("origin", germanEntry.getKey());
assertEquals("origin", untranslatedEntry.getKey());
assertEquals("Cologne-Bonn: CGN", englishEntry.getValue());
assertEquals("Cologne-Bonn: CGN", germanEntry.getValue());
assertEquals("Cologne-Bonn: CGN", untranslatedEntry.getValue());
// 4
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("destination", englishEntry.getKey());
assertEquals("destination", germanEntry.getKey());
assertEquals("destination", untranslatedEntry.getKey());
assertEquals("Dubrovnik: DBV", englishEntry.getValue());
assertEquals("Dubrovnik: DBV", germanEntry.getValue());
assertEquals("Dubrovnik: DBV", untranslatedEntry.getValue());
// 5
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("name", englishEntry.getKey());
assertEquals("name", germanEntry.getKey());
assertEquals("name", untranslatedEntry.getKey());
assertEquals("Name: John Doe", englishEntry.getValue());
assertEquals("Name: John Doe", germanEntry.getValue());
assertEquals("name_str: John Doe", untranslatedEntry.getValue());
// 6
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("status", englishEntry.getKey());
assertEquals("status", germanEntry.getKey());
assertEquals("status", untranslatedEntry.getKey());
assertEquals("Status: -", englishEntry.getValue());
assertEquals("Status: -", germanEntry.getValue());
assertEquals("status_str: -", untranslatedEntry.getValue());
// 7
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("boardinggroup", englishEntry.getKey());
assertEquals("boardinggroup", germanEntry.getKey());
assertEquals("boardinggroup", untranslatedEntry.getKey());
assertEquals("Group: GROUP 1", englishEntry.getValue());
assertEquals("Gruppe: GROUP 1", germanEntry.getValue());
assertEquals("boardinggroup_str: GROUP 1", untranslatedEntry.getValue());
// 8
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("tarif", englishEntry.getKey());
assertEquals("tarif", germanEntry.getKey());
assertEquals("tarif", untranslatedEntry.getKey());
assertEquals("Fare: SMART", englishEntry.getValue());
assertEquals("Tarif: SMART", germanEntry.getValue());
assertEquals("fare_str: SMART", untranslatedEntry.getValue());
// 9
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("flightNumber", englishEntry.getKey());
assertEquals("flightNumber", germanEntry.getKey());
assertEquals("flightNumber", untranslatedEntry.getKey());
assertEquals("Flight: EW 954", englishEntry.getValue());
assertEquals("Flug: EW 954", germanEntry.getValue());
assertEquals("flight_str: EW 954", untranslatedEntry.getValue());
// 10
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("departureDate", englishEntry.getKey());
assertEquals("departureDate", germanEntry.getKey());
assertEquals("departureDate", untranslatedEntry.getKey());
assertEquals("Date: 08/09/2019", englishEntry.getValue());
assertEquals("Datum: 08/09/2019", germanEntry.getValue());
assertEquals("date_str: 08/09/2019", untranslatedEntry.getValue());
// 11
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("boarding", englishEntry.getKey());
assertEquals("boarding", germanEntry.getKey());
assertEquals("boarding", untranslatedEntry.getKey());
assertEquals("Boarding: 05:00", englishEntry.getValue());
assertEquals("Boarding: 05:00", germanEntry.getValue());
assertEquals("boarding_str: 05:00", untranslatedEntry.getValue());
// 12
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("closure", englishEntry.getKey());
assertEquals("closure", germanEntry.getKey());
assertEquals("closure", untranslatedEntry.getKey());
assertEquals("Gate closure: 05:15", englishEntry.getValue());
assertEquals("Gate Schließt: 05:15", germanEntry.getValue());
assertEquals("closure_str: 05:15", untranslatedEntry.getValue());
// 13
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("info", englishEntry.getKey());
assertEquals("info", germanEntry.getKey());
assertEquals("info", untranslatedEntry.getKey());
assertEquals("Eurowings wishes you a pleasant flight .\r\n" +
"\r\n" +
"We kindly ask you to be present at your departure gate on time.", englishEntry.getValue());
assertEquals("Eurowings wünscht Ihnen einen angenehmen Flug.\r\n" +
"\r\n" +
"Wir bitten Sie, sich zur angegeben Boarding Zeit am Gate einzufinden.", germanEntry.getValue());
assertEquals("info_content_str", untranslatedEntry.getValue());
// 14
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("recordlocator", englishEntry.getKey());
assertEquals("recordlocator", germanEntry.getKey());
assertEquals("recordlocator", untranslatedEntry.getKey());
assertEquals("Booking code: JBZPPP", englishEntry.getValue());
assertEquals("Buchungscode: JBZPPP", germanEntry.getValue());
assertEquals("recordlocator_str: JBZPPP", untranslatedEntry.getValue());
// 15
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("sequence", englishEntry.getKey());
assertEquals("sequence", germanEntry.getKey());
assertEquals("sequence", untranslatedEntry.getKey());
assertEquals("Sequence: 73", englishEntry.getValue());
assertEquals("Sequenz: 73", germanEntry.getValue());
assertEquals("sequence_str: 73", untranslatedEntry.getValue());
// 16
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("notice", englishEntry.getKey());
assertEquals("notice", germanEntry.getKey());
assertEquals("notice", untranslatedEntry.getKey());
assertEquals("Notice: Please note that although your flight may be delayed, you will still need to check in and go to your departure gate on time as scheduled.\r\n" +
"\r\n" +
"Carry on one item of hand luggage (8 kg, 55 x 40 x 23 cm) for free.", englishEntry.getValue());
assertEquals("Hinweis: Bitte beachten Sie, dass obwohl Ihr Flug verspätet sein mag, Sie dennoch wie geplant pünktlich am Check-in und am Abfluggate erscheinen müssen.\r\n" +
"\r\n" +
"Kostenlose Mitnahme eines Handgepäckstücks (8 Kg, 55 x 40 x 23cm).", germanEntry.getValue());
assertEquals("notice_str: notice_content_str", untranslatedEntry.getValue());
// 17
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("baggage", englishEntry.getKey());
assertEquals("baggage", germanEntry.getKey());
assertEquals("baggage", untranslatedEntry.getKey());
assertEquals("Carrying liquids in hand luggage: In addition to other restrictions on hand luggage, there are still restrictions on liquids and gels brought by the passenger or purchased before the security control on all departures within the European Union, as well as to many other countries (including Switzerland, Russia, Iceland, Croatia, Israel, Egypt, Morocco, Tunisia and Norway):\r\n" +
"\r\n" +
"- All liquids (such as toiletries and cosmetics, gels, pastes, creams, lotions, mixtures of liquids and solids, perfumes, pressurised containers, cans, water bottles etc) as well as wax and gel-like substances may only be carried on board in amounts less than 100ml or 100g.\r\n" +
"\r\n" +
"- These liquids or substances must be packed in closed containers in a transparent, re-sealable plastic bag (max. contents 1 kg).\r\n" +
"\r\n" +
"- It is the passengers responsibility to purchase this bag before departure. They are available in many supermarkets, e.g. as freezer bags. It is currently not possible for passengers to obtain or purchase the required bags from Eurowings check-in.\r\n" +
"\r\n" +
"- Prescription medicines and baby food may still be carried in hand baggage. The passenger must prove that such medicines and/or baby food are needed during the flight.\r\n" +
"\r\n" +
"- Products and bags which do not meet the requirements or are only sealed with a rubber band or similar will unfortunately have to be surrendered by passengers\r\n" +
"\r\n" +
"In order to pass through the airport as quickly as possible, you are strongly advised to pack any liquids or gels which are not essential for your journey on board the aircraft in your checked baggage if possible.\r\n" +
"\r\n" +
"As a matter of course, liquids from the Travel Value / Duty Free shops which have been purchased after you have passed through security are still allowed on board.\r\n" +
"\r\n" +
"Eurowings shall not be liable for any items which passengers are prohibited from carrying in their hand baggage for security reasons and are required to surrender at the security checkpoint.", englishEntry.getValue());
assertEquals("Mitnahme von Flüssigkeiten im Handgepäck: Neben den sonstigen Beschränkungen für das Handgepäck ist für alle Abflüge innerhalb der Europäischen Union sowie vielen weiteren Ländern (u.a. Schweiz, Russland, Island, Kroatien, Israel, Ägypten, Marokko, Tunesien, Norwegen) die Mitnahme von vor der Fluggastkontrolle erworbenen bzw. mitgebrachten Flüssigkeiten und Gels nur noch eingeschränkt erlaubt:\r\n" +
"\r\n" +
"- Sämtliche Flüssigkeiten (wie Kosmetik- und Toilettenartikel, Gels, Pasten, Cremes, Lotionen, Gemische aus flüssigen und festen Stoffen, Parfums, Behälter unter Druck, Dosen, Wasserflaschen etc.) sowie wachs- oder gelartige Stoffe dürfen nur noch in Behältnissen bis zu 100 ml bzw. 100 g mit an Bord genommen werden.\r\n" +
"\r\n" +
"- Diese Flüssigkeiten bzw. Stoffe müssen in einem transparenten, wiederverschließbaren Plastikbeutel (max. 1 kg Inhalt) vollständig geschlossen, verpackt sein.\r\n" +
"\r\n" +
"- Diese Beutel müssen Fluggäste selbst vor dem Abflug erwerben. Sie sind in vielen Supermärkten z. B. als Gefrierbeutel erhältlich. Es besteht zurzeit keine Möglichkeit, entsprechende Plastikbeutel am Eurowings Check-In zu erwerben bzw. auszugeben.\r\n" +
"\r\n" +
"- Verschreibungspflichtige Medikamente sowie Babynahrung dürfen weiterhin im Handgepäck transportiert werden. Der Fluggast muss nachweisen, dass die Medikamente und Babynahrung während des Fluges benötigt werden.\r\n" +
"\r\n" +
"- Produkte und Beutel, die nicht den Maßgaben entsprechen oder die nur mit Gummiband oder ähnlichem verschlossen sind, müssen leider abgegeben werden.\r\n" +
"\r\n" +
"Flüssigkeiten und Gels, die Sie nicht zwingend während Ihres Aufenthalts an Bord benötigen, sollten zur raschen Fluggastabfertigung nach Möglichkeit im aufzugebenden Gepäck untergebracht werden.\r\n" +
"\r\n" +
"Selbstverständlich ist die Mitnahme von allen Flüssigkeiten/Gels/Getränken aus Travel-Value oder Duty Free-Shops, die nach der Fluggastkontrolle erworben werden, weiterhin erlaubt.\r\n" +
"\r\n" +
"Eurowings übernimmt keine Haftung für Gegenstände, die der Fluggast nicht im Handgepäck mitführen darf und deshalb aus Sicherheitsgründen an der Fluggastkontrolle abgeben muss.", germanEntry.getValue());
assertEquals("baggage_str: baggage_content_str", untranslatedEntry.getValue());
// 18
englishEntry = englishKeys.next();
germanEntry = germanKeys.next();
untranslatedEntry = untranslatedKeys.next();
assertEquals("contact", englishEntry.getKey());
assertEquals("contact", germanEntry.getKey());
assertEquals("contact", untranslatedEntry.getKey());
assertEquals("Contact: https://mobile.eurowings.com/booking/StaticContactInfo.aspx?culture=en-GB&back=home", englishEntry.getValue());
assertEquals("Kontakt: Sie erreichen das deutsche Call Center unter der Telefonnummer\r\n" +
"\r\n" +
"0180 6 320 320 ( 0:00 Uhr - 24:00 Uhr )\r\n" +
"\r\n" +
"(0,20 € pro Anruf aus dem Festnetz der Deutschen Telekom - Mobilfunk maximal 0,60 € pro Anruf).", germanEntry.getValue());
assertEquals("contact_str: contact_content_str", untranslatedEntry.getValue());
}
}

View File

Binary file not shown.

View File

Binary file not shown.