diff --git a/app/build.gradle b/app/build.gradle index 32c9123ac..a01de9d38 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -40,6 +40,7 @@ dependencies { compile 'com.journeyapps:zxing-android-embedded:3.2.0@aar' compile 'com.google.zxing:core:3.2.1' compile 'org.apache.commons:commons-csv:1.2' + compile group: 'com.google.guava', name: 'guava', version: '18.0' testCompile 'junit:junit:4.12' testCompile "org.robolectric:robolectric:3.0" } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f55a3bfed..59b46dad5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -36,14 +36,18 @@ android:name=".LoyaltyCardViewActivity" android:theme="@style/AppTheme.NoActionBar" android:configChanges="orientation|screenSize" - android:windowSoftInputMode="stateHidden" - android:parentActivityName="protect.card_locker.MainActivity"/> + android:windowSoftInputMode="stateHidden"/> + + android:theme="@style/AppTheme.NoActionBar"/> diff --git a/app/src/main/java/protect/card_locker/BarcodeImageWriterTask.java b/app/src/main/java/protect/card_locker/BarcodeImageWriterTask.java index d71bee2ac..b7e5642e9 100644 --- a/app/src/main/java/protect/card_locker/BarcodeImageWriterTask.java +++ b/app/src/main/java/protect/card_locker/BarcodeImageWriterTask.java @@ -3,6 +3,7 @@ package protect.card_locker; import android.graphics.Bitmap; import android.os.AsyncTask; import android.util.Log; +import android.view.View; import android.widget.ImageView; import com.google.zxing.BarcodeFormat; @@ -45,7 +46,17 @@ class BarcodeImageWriterTask extends AsyncTask BitMatrix bitMatrix; try { - bitMatrix = writer.encode(cardId, format, imageWidth, imageHeight, null); + try + { + bitMatrix = writer.encode(cardId, format, imageWidth, imageHeight, null); + } + catch(Exception e) + { + // Cast a wider net here and catch any exception, as there are some + // cases where an encoder may fail if the data is invalid for the + // barcode type. If this happens, we want to fail gracefully. + throw new WriterException(e); + } final int WHITE = 0xFFFFFFFF; final int BLACK = 0xFF000000; @@ -86,9 +97,9 @@ class BarcodeImageWriterTask extends AsyncTask return bitmap; } - catch (WriterException | IllegalArgumentException e) + catch (WriterException e) { - Log.e(TAG, "Failed to generate barcode", e); + Log.e(TAG, "Failed to generate barcode of type " + format + ": " + cardId, e); } return null; @@ -96,6 +107,7 @@ class BarcodeImageWriterTask extends AsyncTask protected void onPostExecute(Bitmap result) { + Log.i(TAG, "Finished generating barcode image of type " + format + ": " + cardId); ImageView imageView = imageViewReference.get(); if(imageView == null) { @@ -104,5 +116,16 @@ class BarcodeImageWriterTask extends AsyncTask } imageView.setImageBitmap(result); + + if(result != null) + { + Log.i(TAG, "Displaying barcode"); + imageView.setVisibility(View.VISIBLE); + } + else + { + Log.i(TAG, "Barcode generation failed, removing image from display"); + imageView.setVisibility(View.GONE); + } } } \ No newline at end of file diff --git a/app/src/main/java/protect/card_locker/BarcodeSelectorActivity.java b/app/src/main/java/protect/card_locker/BarcodeSelectorActivity.java new file mode 100644 index 000000000..10dcb8e24 --- /dev/null +++ b/app/src/main/java/protect/card_locker/BarcodeSelectorActivity.java @@ -0,0 +1,202 @@ +package protect.card_locker; + +import android.app.Activity; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewTreeObserver; +import android.widget.EditText; +import android.widget.ImageView; + +import com.google.common.collect.ImmutableMap; +import com.google.zxing.BarcodeFormat; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.Map; + +/** + * This activity is callable and will allow a user to enter + * barcode data and generate all barcodes possible for + * the data. The user may then select any barcode, where its + * data and type will be returned to the caller. + */ +public class BarcodeSelectorActivity extends AppCompatActivity +{ + private static final String TAG = "LoyaltyCardLocker"; + + // Result this activity will return + public static final String BARCODE_CONTENTS = "contents"; + public static final String BARCODE_FORMAT = "format"; + + // These are all the barcode types that the zxing library + // is able to generate a barcode for, and thus should be + // the only barcodes which we should attempt to scan. + public static final Collection SUPPORTED_BARCODE_TYPES = Collections.unmodifiableList( + Arrays.asList( + BarcodeFormat.AZTEC.name(), + BarcodeFormat.CODE_39.name(), + BarcodeFormat.CODE_128.name(), + BarcodeFormat.CODABAR.name(), + BarcodeFormat.DATA_MATRIX.name(), + BarcodeFormat.EAN_8.name(), + BarcodeFormat.EAN_13.name(), + BarcodeFormat.ITF.name(), + BarcodeFormat.PDF_417.name(), + BarcodeFormat.QR_CODE.name(), + BarcodeFormat.UPC_A.name() + )); + + private Map barcodeViewMap; + private LinkedList barcodeGeneratorTasks = new LinkedList<>(); + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + setContentView(R.layout.barcode_selector_activity); + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + ActionBar actionBar = getSupportActionBar(); + if(actionBar != null) + { + actionBar.setDisplayHomeAsUpEnabled(true); + } + + barcodeViewMap = ImmutableMap.builder() + .put(BarcodeFormat.AZTEC.name(), R.id.aztecBarcode) + .put(BarcodeFormat.CODE_39.name(), R.id.code39Barcode) + .put(BarcodeFormat.CODE_128.name(), R.id.code128Barcode) + .put(BarcodeFormat.CODABAR.name(), R.id.codabarBarcode) + .put(BarcodeFormat.DATA_MATRIX.name(), R.id.datamatrixBarcode) + .put(BarcodeFormat.EAN_8.name(), R.id.ean8Barcode) + .put(BarcodeFormat.EAN_13.name(), R.id.ean13Barcode) + .put(BarcodeFormat.ITF.name(), R.id.itfBarcode) + .put(BarcodeFormat.PDF_417.name(), R.id.pdf417Barcode) + .put(BarcodeFormat.QR_CODE.name(), R.id.qrcodeBarcode) + .put(BarcodeFormat.UPC_A.name(), R.id.upcaBarcode) + .build(); + + EditText cardId = (EditText) findViewById(R.id.cardId); + cardId.addTextChangedListener(new TextWatcher() + { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) + { + // Noting to do + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) + { + Log.d(TAG, "Entered text: " + s); + + // Stop any async tasks which may not have been started yet + for(AsyncTask task : barcodeGeneratorTasks) + { + task.cancel(false); + } + barcodeGeneratorTasks.clear(); + + // Update barcodes + for(String key : barcodeViewMap.keySet()) + { + ImageView image = (ImageView)findViewById(barcodeViewMap.get(key)); + createBarcodeOption(image, key, s.toString()); + } + } + + @Override + public void afterTextChanged(Editable s) + { + // Noting to do + } + }); + } + + private void createBarcodeOption(final ImageView image, final String formatType, final String cardId) + { + final BarcodeFormat format = BarcodeFormat.valueOf(formatType); + if(format == null) + { + Log.w(TAG, "Unsupported barcode format: " + formatType); + return; + } + + image.setImageBitmap(null); + image.setVisibility(View.GONE); + image.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View v) + { + Log.d(TAG, "Selected barcode type " + formatType); + Intent result = new Intent(); + result.putExtra(BARCODE_FORMAT, formatType); + result.putExtra(BARCODE_CONTENTS, cardId); + BarcodeSelectorActivity.this.setResult(RESULT_OK, result); + finish(); + } + }); + + if(image.getHeight() == 0) + { + // The size of the ImageView is not yet available as it has not + // yet been drawn. Wait for it to be drawn so the size is available. + image.getViewTreeObserver().addOnGlobalLayoutListener( + new ViewTreeObserver.OnGlobalLayoutListener() + { + @Override + public void onGlobalLayout() + { + Log.d(TAG, "Global layout finished, type: + " + formatType + ", width: " + image.getWidth()); + if (Build.VERSION.SDK_INT < 16) + { + image.getViewTreeObserver().removeGlobalOnLayoutListener(this); + } + else + { + image.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } + + Log.d(TAG, "Generating barcode for type " + formatType); + BarcodeImageWriterTask task = new BarcodeImageWriterTask(image, cardId, format); + barcodeGeneratorTasks.add(task); + task.execute(); + } + }); + } + else + { + Log.d(TAG, "Generating barcode for type " + formatType); + BarcodeImageWriterTask task = new BarcodeImageWriterTask(image, cardId, format); + barcodeGeneratorTasks.add(task); + task.execute(); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + if (item.getItemId() == android.R.id.home) + { + setResult(Activity.RESULT_CANCELED); + finish(); + return true; + } + + return super.onOptionsItemSelected(item); + } +} diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java b/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java index a11840d91..3b8f72a9c 100644 --- a/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java +++ b/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java @@ -1,7 +1,9 @@ package protect.card_locker; +import android.app.Activity; import android.content.Intent; +import android.os.Build; import android.os.Bundle; import android.support.design.widget.Snackbar; import android.support.v7.app.ActionBar; @@ -20,31 +22,12 @@ import com.google.zxing.BarcodeFormat; import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentResult; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; - public class LoyaltyCardViewActivity extends AppCompatActivity { private static final String TAG = "CardLocker"; - // These are all the barcode types that the zxing library - // is able to generate a barcode for, and thus should be - // the only barcodes which we should attempt to scan. - Collection supportedBarcodeTypes = Collections.unmodifiableList(Arrays.asList( - BarcodeFormat.AZTEC.name(), - BarcodeFormat.CODE_39.name(), - BarcodeFormat.CODE_128.name(), - BarcodeFormat.CODABAR.name(), - BarcodeFormat.DATA_MATRIX.name(), - BarcodeFormat.EAN_8.name(), - BarcodeFormat.EAN_13.name(), - BarcodeFormat.ITF.name(), - BarcodeFormat.PDF_417.name(), - BarcodeFormat.QR_CODE.name(), - BarcodeFormat.UPC_A.name() - )); + private static final int SELECT_BARCODE_REQUEST = 1; @Override protected void onCreate(Bundle savedInstanceState) @@ -84,6 +67,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity final View barcodeCaptureLayout = findViewById(R.id.barcodeCaptureLayout); final Button captureButton = (Button) findViewById(R.id.captureButton); + final Button enterButton = (Button) findViewById(R.id.enterButton); final Button saveButton = (Button) findViewById(R.id.saveButton); final Button cancelButton = (Button) findViewById(R.id.cancelButton); @@ -161,6 +145,15 @@ public class LoyaltyCardViewActivity extends AppCompatActivity @Override public void onGlobalLayout() { + if (Build.VERSION.SDK_INT < 16) + { + barcodeImage.getViewTreeObserver().removeGlobalOnLayoutListener(this); + } + else + { + barcodeImage.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } + Log.d(TAG, "ImageView size now known"); new BarcodeImageWriterTask(barcodeImage, cardIdString, format).execute(); } @@ -182,7 +175,7 @@ public class LoyaltyCardViewActivity extends AppCompatActivity public void onClick(View v) { IntentIntegrator integrator = new IntentIntegrator(LoyaltyCardViewActivity.this); - integrator.setDesiredBarcodeFormats(supportedBarcodeTypes); + integrator.setDesiredBarcodeFormats(BarcodeSelectorActivity.SUPPORTED_BARCODE_TYPES); String prompt = getResources().getString(R.string.scanCardBarcode); integrator.setPrompt(prompt); @@ -192,6 +185,16 @@ public class LoyaltyCardViewActivity extends AppCompatActivity captureButton.setOnClickListener(captureCallback); + enterButton.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View v) + { + Intent i = new Intent(getApplicationContext(), BarcodeSelectorActivity.class); + startActivityForResult(i, SELECT_BARCODE_REQUEST); + } + }); + saveButton.setOnClickListener(new View.OnClickListener() { @Override @@ -267,6 +270,10 @@ public class LoyaltyCardViewActivity extends AppCompatActivity switch(id) { + case android.R.id.home: + finish(); + break; + case R.id.action_delete: Log.e(TAG, "Deleting card: " + loyaltyCardId); @@ -291,24 +298,37 @@ public class LoyaltyCardViewActivity extends AppCompatActivity @Override public void onActivityResult(int requestCode, int resultCode, Intent intent) { + String contents = null; + String format = null; + IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); if (result != null) { - String contents = result.getContents(); - String format = result.getFormatName(); - if(contents != null && contents.isEmpty() == false && - format != null && format.isEmpty() == false) - { - Log.i(TAG, "Read Contents from scan: " + contents); - Log.i(TAG, "Read Format: " + format); + Log.i(TAG, "Received barcode information from capture"); + contents = result.getContents(); + format = result.getFormatName(); + } - final EditText cardIdField = (EditText) findViewById(R.id.cardId); - cardIdField.setText(contents); - final EditText barcodeTypeField = (EditText) findViewById(R.id.barcodeType); - barcodeTypeField.setText(format); - onResume(); - } + if(requestCode == SELECT_BARCODE_REQUEST && resultCode == Activity.RESULT_OK) + { + Log.i(TAG, "Received barcode information from capture"); + + contents = intent.getStringExtra(BarcodeSelectorActivity.BARCODE_CONTENTS); + format = intent.getStringExtra(BarcodeSelectorActivity.BARCODE_FORMAT); + } + + if(contents != null && contents.isEmpty() == false && + format != null && format.isEmpty() == false) + { + Log.i(TAG, "Read barcode id: " + contents); + Log.i(TAG, "Read format: " + format); + + final EditText cardIdField = (EditText) findViewById(R.id.cardId); + cardIdField.setText(contents); + final EditText barcodeTypeField = (EditText) findViewById(R.id.barcodeType); + barcodeTypeField.setText(format); + onResume(); } } } \ No newline at end of file diff --git a/app/src/main/java/protect/card_locker/MainActivity.java b/app/src/main/java/protect/card_locker/MainActivity.java index 72ad9e9c4..42deefbd0 100644 --- a/app/src/main/java/protect/card_locker/MainActivity.java +++ b/app/src/main/java/protect/card_locker/MainActivity.java @@ -123,6 +123,7 @@ public class MainActivity extends AppCompatActivity final String[][] USED_LIBRARIES = new String[][] { new String[] {"Commons CSV", "https://commons.apache.org/proper/commons-csv/"}, + new String[] {"Guava", "https://github.com/google/guava"}, new String[] {"ZXing", "https://github.com/zxing/zxing"}, new String[] {"ZXing Android Embedded", "https://github.com/journeyapps/zxing-android-embedded"}, }; diff --git a/app/src/main/res/layout/barcode_selector_activity.xml b/app/src/main/res/layout/barcode_selector_activity.xml new file mode 100644 index 000000000..e5884af2e --- /dev/null +++ b/app/src/main/res/layout/barcode_selector_activity.xml @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/loyalty_card_view_activity.xml b/app/src/main/res/layout/loyalty_card_view_activity.xml index 03aedbadb..c42b02138 100644 --- a/app/src/main/res/layout/loyalty_card_view_activity.xml +++ b/app/src/main/res/layout/loyalty_card_view_activity.xml @@ -1,11 +1,9 @@ + android:fitsSystemWindows="true"> +