diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c74acd..e10309a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ card.io Android SDK change log and release notes ================================================ +5.5.0 +----- +* Update Gradle build plugin to 2.2.3. +* Update compile and target SDK versions to 25. +* Update NDK to r13b. +* Increase `minSdkVersion` to 16. +* Upgrade OpenCV to 2.4.13. + 5.4.2 ----- * Add Mastercard 2-series support. diff --git a/README.md b/README.md index bd90015..f1f65e6 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ All releases follow [semantic versioning](http://semver.org/). The latest version is available via `mavenCentral()`. Just add the following dependency: -```gradle -compile 'io.card:android-sdk:5.4.2' +``` +compile 'io.card:android-sdk:5.5.0' ``` You can receive updates about new versions via a few different channels: @@ -45,8 +45,8 @@ A manual entry fallback mode is provided for devices that do not meet these requ ##### If you use gradle, then add the following dependency from `mavenCentral()`: -```gradle -compile 'io.card:android-sdk:5.4.2' +``` +compile 'io.card:android-sdk:5.5.0' ``` ##### If you use something other than gradle, then: diff --git a/SampleApp/build.gradle b/SampleApp/build.gradle index ac8ec08..7a5d303 100644 --- a/SampleApp/build.gradle +++ b/SampleApp/build.gradle @@ -3,19 +3,31 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.0' + classpath 'com.android.tools.build:gradle:2.2.3' } } repositories { jcenter() + maven { + url "https://jitpack.io" + } } apply plugin: 'com.android.application' android { - compileSdkVersion 24 - buildToolsVersion "24.0.1" + compileSdkVersion 25 + buildToolsVersion "25.0.0" + + defaultConfig { + applicationId "io.card.development" + minSdkVersion 18 + targetSdkVersion 25 + versionCode 1 + versionName "1.0.0" + testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner' + } signingConfigs { myConfig { @@ -27,10 +39,6 @@ android { } buildTypes { - debug { - applicationIdSuffix ".debug" - } - release { minifyEnabled true proguardFile getDefaultProguardFile('proguard-android.txt') @@ -52,8 +60,13 @@ dependencies { if (parent != null) { compile project(':card.io') } else { - compile 'io.card:android-sdk:5.4.2' + compile 'io.card:android-sdk:5.5.0' } + + debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4' + + androidTestCompile "com.android.support.test.espresso:espresso-core:2.2.2" + androidTestCompile "com.github.lkorth:device-automator:01d85912ec" } task wrapper(type: Wrapper) { diff --git a/SampleApp/src/androidTest/assets/test_card_images/README.md b/SampleApp/src/androidTest/assets/test_card_images/README.md new file mode 100644 index 0000000..5cc04be --- /dev/null +++ b/SampleApp/src/androidTest/assets/test_card_images/README.md @@ -0,0 +1,4 @@ +# Test Card Images + +Card images in this folder are included for automated testing. Images should be 640px x 480px for +tests. diff --git a/SampleApp/src/androidTest/assets/test_card_images/amex.png b/SampleApp/src/androidTest/assets/test_card_images/amex.png new file mode 100644 index 0000000..e75f9c9 Binary files /dev/null and b/SampleApp/src/androidTest/assets/test_card_images/amex.png differ diff --git a/SampleApp/src/androidTest/assets/test_card_images/amex_large.png b/SampleApp/src/androidTest/assets/test_card_images/amex_large.png new file mode 100644 index 0000000..5850b87 Binary files /dev/null and b/SampleApp/src/androidTest/assets/test_card_images/amex_large.png differ diff --git a/SampleApp/src/androidTest/java/io/card/payment/CardScannerTester.java b/SampleApp/src/androidTest/java/io/card/payment/CardScannerTester.java new file mode 100644 index 0000000..c8a2d72 --- /dev/null +++ b/SampleApp/src/androidTest/java/io/card/payment/CardScannerTester.java @@ -0,0 +1,113 @@ +package io.card.payment; + +/* CardScannerTester.java + * See the file "LICENSE.md" for the full license governing this code. + */ + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Handler; +import android.view.SurfaceHolder; + +import java.io.IOException; + +import static android.support.test.InstrumentationRegistry.getInstrumentation; + +public class CardScannerTester extends CardScanner { + + private static final long FRAME_INTERVAL = (long) (1000.0 / 30); + + private static String sCardAssetName; + + private boolean mScanAllowed; + private Handler mHandler; + private byte[] mFrame; + + public static void setCardAsset(String cardAssetName) { + sCardAssetName = cardAssetName; + } + + public CardScannerTester(CardIOActivity scanActivity, int currentFrameOrientation) { + super(scanActivity, currentFrameOrientation); + useCamera = false; + mScanAllowed = false; + mHandler = new Handler(); + + try { + Bitmap bitmap = BitmapFactory.decodeStream(getInstrumentation().getContext().getAssets() + .open("test_card_images/" + sCardAssetName)); + mFrame = getNV21FormattedImage(bitmap.getWidth(), bitmap.getHeight(), bitmap); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private Runnable mFrameRunnable = new Runnable() { + @Override + public void run() { + if (!mScanAllowed) { + return; + } + + onPreviewFrame(mFrame, null); + mHandler.postDelayed(this, FRAME_INTERVAL); + } + }; + + @Override + boolean resumeScanning(SurfaceHolder holder) { + boolean result = super.resumeScanning(holder); + mScanAllowed = true; + mHandler.postDelayed(mFrameRunnable, FRAME_INTERVAL); + return result; + } + + @Override + public void pauseScanning() { + mScanAllowed = false; + super.pauseScanning(); + } + + private byte[] getNV21FormattedImage(int width, int height, Bitmap bitmap) { + int [] argb = new int[width * height]; + byte [] yuv = new byte[width * height * 3 / 2]; + + bitmap.getPixels(argb, 0, width, 0, 0, width, height); + encodeYUV420SP(yuv, argb, width, height); + bitmap.recycle(); + + return yuv; + } + + private void encodeYUV420SP(byte[] yuv420sp, int[] argb, int width, int height) { + int frameSize = width * height; + int yIndex = 0; + int uvIndex = frameSize; + + int R, G, B, Y, U, V; + int index = 0; + for (int j = 0; j < height; j++) { + for (int i = 0; i < width; i++) { + R = (argb[index] & 0xff0000) >> 16; + G = (argb[index] & 0xff00) >> 8; + B = (argb[index] & 0xff); + + // well known RGB to YUV algorithm + Y = ( ( 66 * R + 129 * G + 25 * B + 128) >> 8) + 16; + U = ( ( -38 * R - 74 * G + 112 * B + 128) >> 8) + 128; + V = ( ( 112 * R - 94 * G - 18 * B + 128) >> 8) + 128; + + // NV21 has a plane of Y and interleaved planes of VU each sampled by a factor of 2 + // meaning for every 4 Y pixels there are 1 V and 1 U. Note the sampling is every + // other pixel AND every other scanline. + yuv420sp[yIndex++] = (byte) ((Y < 0) ? 0 : ((Y > 255) ? 255 : Y)); + if (j % 2 == 0 && index % 2 == 0) { + yuv420sp[uvIndex++] = (byte)((V<0) ? 0 : ((V > 255) ? 255 : V)); + yuv420sp[uvIndex++] = (byte)((U<0) ? 0 : ((U > 255) ? 255 : U)); + } + + index ++; + } + } + } +} diff --git a/SampleApp/src/androidTest/java/io/card/test/CardIOActivityTest.java b/SampleApp/src/androidTest/java/io/card/test/CardIOActivityTest.java new file mode 100644 index 0000000..b8b9743 --- /dev/null +++ b/SampleApp/src/androidTest/java/io/card/test/CardIOActivityTest.java @@ -0,0 +1,72 @@ +package io.card.test; + +import android.Manifest; +import android.app.Activity; +import android.content.Intent; + +import org.junit.Rule; +import org.junit.Test; + +import java.lang.reflect.Field; + +import io.card.payment.CardIOActivity; +import io.card.payment.CardScannerTester; +import io.card.payment.CreditCard; + +import static com.lukekorth.deviceautomator.DeviceAutomator.onDevice; +import static junit.framework.Assert.assertEquals; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +public class CardIOActivityTest { + + private CardIOTestActivity mActivity; + + @Rule + public final CustomActivityTestRule mActivityTestRule = + new CustomActivityTestRule<>(CardIOTestActivity.class, false, false); + + @Test(timeout = 30000) + public void scansAmexCards() { + CardScannerTester.setCardAsset("amex.png"); + + startScan(); + + waitForActivityToFinish(); + CreditCard result = getActivityResultIntent().getParcelableExtra(CardIOActivity.EXTRA_SCAN_RESULT); + assertEquals("3743 260055 74998", result.getFormattedCardNumber()); + } + + private void startScan() { + mActivityTestRule.launchActivity(null); + mActivity = mActivityTestRule.getActivity(); + + onDevice().acceptRuntimePermission(Manifest.permission.CAMERA); + } + + private void waitForActivityToFinish() { + long endTime = System.currentTimeMillis() + 10000; + + do { + try { + if (mActivity.isFinishing()) { + return; + } + } catch (Exception ignored) {} + } while (System.currentTimeMillis() < endTime); + + throw new RuntimeException("Maximum wait elapsed (10s) while waiting for activity to finish"); + } + + private Intent getActivityResultIntent() { + assertThat("Activity did not finish", mActivity.isFinishing(), is(true)); + + try { + Field resultDataField = Activity.class.getDeclaredField("mResultData"); + resultDataField.setAccessible(true); + return (Intent) resultDataField.get(mActivity); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/SampleApp/src/androidTest/java/io/card/test/CustomActivityTestRule.java b/SampleApp/src/androidTest/java/io/card/test/CustomActivityTestRule.java new file mode 100644 index 0000000..34d11ec --- /dev/null +++ b/SampleApp/src/androidTest/java/io/card/test/CustomActivityTestRule.java @@ -0,0 +1,41 @@ +package io.card.test; + + +import android.app.Activity; +import android.app.KeyguardManager; +import android.content.Context; + +import static android.support.test.InstrumentationRegistry.getTargetContext; + +public class CustomActivityTestRule extends android.support.test.rule.ActivityTestRule { + + private KeyguardManager.KeyguardLock mKeyguardLock; + + public CustomActivityTestRule(Class activityClass) { + super(activityClass); + init(); + } + + public CustomActivityTestRule(Class activityClass, boolean initialTouchMode) { + super(activityClass, initialTouchMode); + init(); + } + + public CustomActivityTestRule(Class activityClass, boolean initialTouchMode, boolean launchActivity) { + super(activityClass, initialTouchMode, launchActivity); + init(); + } + + private void init() { + mKeyguardLock = ((KeyguardManager) getTargetContext().getSystemService(Context.KEYGUARD_SERVICE)) + .newKeyguardLock("CardIOTest"); + mKeyguardLock.disableKeyguard(); + } + + @Override + protected void afterActivityFinished() { + super.afterActivityFinished(); + + mKeyguardLock.reenableKeyguard(); + } +} diff --git a/SampleApp/src/androidTest/java/io/card/test/SampleActivityTest.java b/SampleApp/src/androidTest/java/io/card/test/SampleActivityTest.java new file mode 100644 index 0000000..23efd53 --- /dev/null +++ b/SampleApp/src/androidTest/java/io/card/test/SampleActivityTest.java @@ -0,0 +1,89 @@ +package io.card.test; + +import android.Manifest; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import io.card.development.R; +import io.card.development.SampleActivity; +import io.card.payment.i18n.LocalizedStrings; +import io.card.payment.i18n.StringKey; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.typeText; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static com.lukekorth.deviceautomator.DeviceAutomator.onDevice; +import static org.hamcrest.Matchers.containsString; + +public class SampleActivityTest { + + @Rule + public final CustomActivityTestRule mActivityTestRule = + new CustomActivityTestRule<>(SampleActivity.class); + + @Before + public void setup() { + mActivityTestRule.getActivity(); + } + + @Test + public void cancelInManualEntryExistsActivity() { + onView(withText("Force keyboard entry (bypass scan)")).perform(click()); + onView(withText("Scan Credit Card using Card.io")).perform(click()); + onView(withText("Card Number")).check(matches(isDisplayed())); + + onView(withText(LocalizedStrings.getString(StringKey.CANCEL))).perform(click()); + + onView(withText("Force keyboard entry (bypass scan)")).check(matches(isDisplayed())); + } + + @Test + public void manualEntryReturnsCardData() { + onView(withText("Expiry")).perform(click()); + onView(withText("CVV")).perform(click()); + onView(withText("Postal Code")).perform(click()); + onView(withText("Cardholder Name")).perform(click()); + onView(withText("Force keyboard entry (bypass scan)")).perform(click()); + onView(withText("Scan Credit Card using Card.io")).perform(click()); + + fillInCardForm(); + onView(withText(LocalizedStrings.getString(StringKey.DONE))).perform(click()); + + onView(withId(R.id.result)).check(matches(withText(containsString("1111")))); + onView(withId(R.id.result)).check(matches(withText(containsString("Expiry: 12/2022")))); + onView(withId(R.id.result)).check(matches(withText(containsString("CVV: 123")))); + onView(withId(R.id.result)).check(matches(withText(containsString("Postal Code: 95131")))); + onView(withId(R.id.result)).check(matches(withText(containsString("Cardholder Name: John Doe")))); + } + + @Test + public void canEnterManualEntryFromScanActivity() { + onView(withText("Expiry")).perform(click()); + onView(withText("CVV")).perform(click()); + onView(withText("Postal Code")).perform(click()); + onView(withText("Cardholder Name")).perform(click()); + onView(withText("Scan Credit Card using Card.io")).perform(click()); + + onDevice().acceptRuntimePermission(Manifest.permission.CAMERA); + + onView(withText(LocalizedStrings.getString(StringKey.KEYBOARD))).check(matches(isDisplayed())); + onView(withText(LocalizedStrings.getString(StringKey.KEYBOARD))).perform(click()); + + fillInCardForm(); + onView(withText(LocalizedStrings.getString(StringKey.DONE))).perform(click()); + } + + private void fillInCardForm() { + onView(withId(100)).perform(click(), typeText("4111111111111111")); + onView(withId(101)).perform(click(), typeText("1222")); + onView(withId(102)).perform(click(), typeText("123")); + onView(withId(103)).perform(click(), typeText("95131")); + onView(withId(104)).perform(click(), typeText("John Doe")); + } +} diff --git a/SampleApp/src/debug/java/io/card/development/DebugTools.java b/SampleApp/src/debug/java/io/card/development/DebugTools.java new file mode 100644 index 0000000..4bf7783 --- /dev/null +++ b/SampleApp/src/debug/java/io/card/development/DebugTools.java @@ -0,0 +1,27 @@ +package io.card.development; + +import android.app.Application; +import android.os.StrictMode; + +import com.squareup.leakcanary.LeakCanary; + +public class DebugTools { + + public static void setup(Application application) { + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() + .detectAll() + .penaltyLog() + .penaltyDeath() + .build()); + StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() + .detectFileUriExposure() + .detectLeakedClosableObjects() + .detectLeakedRegistrationObjects() + .detectLeakedSqlLiteObjects() + .penaltyLog() + .penaltyDeath() + .build()); + + LeakCanary.install(application); + } +} diff --git a/SampleApp/src/main/AndroidManifest.xml b/SampleApp/src/main/AndroidManifest.xml index 5724955..6919166 100644 --- a/SampleApp/src/main/AndroidManifest.xml +++ b/SampleApp/src/main/AndroidManifest.xml @@ -1,28 +1,24 @@ - - - + package="io.card.development"> - - + + - - + + + - - + - + + diff --git a/SampleApp/src/main/java/io/card/development/SampleActivity.java b/SampleApp/src/main/java/io/card/development/SampleActivity.java new file mode 100644 index 0000000..9179409 --- /dev/null +++ b/SampleApp/src/main/java/io/card/development/SampleActivity.java @@ -0,0 +1,223 @@ +package io.card.development; + +/* SampleActivity.java + * See the file "LICENSE.md" for the full license governing this code. + */ + +import android.app.Activity; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.os.Bundle; +import android.os.Handler; +import android.util.Log; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.Spinner; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +import io.card.payment.CardIOActivity; +import io.card.payment.CardType; +import io.card.payment.CreditCard; +import io.card.payment.i18n.StringKey; +import io.card.payment.i18n.SupportedLocale; +import io.card.payment.i18n.locales.LocalizedStringsList; + +public class SampleActivity extends Activity { + + protected static final String TAG = SampleActivity.class.getSimpleName(); + + private static final int REQUEST_SCAN = 100; + private static final int REQUEST_AUTOTEST = 200; + + private CheckBox mManualToggle; + private CheckBox mEnableExpiryToggle; + private CheckBox mScanExpiryToggle; + private CheckBox mCvvToggle; + private CheckBox mPostalCodeToggle; + private CheckBox mPostalCodeNumericOnlyToggle; + private CheckBox mCardholderNameToggle; + private CheckBox mSuppressManualToggle; + private CheckBox mSuppressConfirmationToggle; + private CheckBox mSuppressScanToggle; + + private TextView mResultLabel; + private ImageView mResultImage; + private ImageView mResultCardTypeImage; + + private boolean autotestMode; + private int numAutotestsPassed; + private CheckBox mUseCardIOLogoToggle; + private CheckBox mShowPayPalActionBarIconToggle; + private CheckBox mKeepApplicationThemeToggle; + private Spinner mLanguageSpinner; + private EditText mUnblurEdit; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.sample_activity); + + mManualToggle = (CheckBox) findViewById(R.id.force_manual); + mEnableExpiryToggle = (CheckBox) findViewById(R.id.gather_expiry); + mScanExpiryToggle = (CheckBox) findViewById(R.id.scan_expiry); + mCvvToggle = (CheckBox) findViewById(R.id.gather_cvv); + mPostalCodeToggle = (CheckBox) findViewById(R.id.gather_postal_code); + mPostalCodeNumericOnlyToggle = (CheckBox) findViewById(R.id.postal_code_numeric_only); + mCardholderNameToggle = (CheckBox) findViewById(R.id.gather_cardholder_name); + mSuppressManualToggle = (CheckBox) findViewById(R.id.suppress_manual); + mSuppressConfirmationToggle = (CheckBox) findViewById(R.id.suppress_confirmation); + mSuppressScanToggle = (CheckBox) findViewById(R.id.detect_only); + + mUseCardIOLogoToggle = (CheckBox) findViewById(R.id.use_card_io_logo); + mShowPayPalActionBarIconToggle = (CheckBox) findViewById(R.id.show_paypal_action_bar_icon); + mKeepApplicationThemeToggle = (CheckBox) findViewById(R.id.keep_application_theme); + + mLanguageSpinner = (Spinner) findViewById(R.id.language); + mUnblurEdit = (EditText) findViewById(R.id.unblur); + + mResultLabel = (TextView) findViewById(R.id.result); + mResultImage = (ImageView) findViewById(R.id.result_image); + mResultCardTypeImage = (ImageView) findViewById(R.id.result_card_type_image); + + TextView version = (TextView) findViewById(R.id.version); + version.setText("card.io library: " + CardIOActivity.sdkVersion() + "\n" + + "Build date: " + CardIOActivity.sdkBuildDate()); + + setScanExpiryEnabled(); + setupLanguageList(); + } + + private void setScanExpiryEnabled() { + mScanExpiryToggle.setEnabled(mEnableExpiryToggle.isChecked()); + } + + public void onExpiryToggle(View v) { + setScanExpiryEnabled(); + } + + public void onScan(View pressed) { + Intent intent = new Intent(this, CardIOActivity.class) + .putExtra(CardIOActivity.EXTRA_NO_CAMERA, mManualToggle.isChecked()) + .putExtra(CardIOActivity.EXTRA_REQUIRE_EXPIRY, mEnableExpiryToggle.isChecked()) + .putExtra(CardIOActivity.EXTRA_SCAN_EXPIRY, mScanExpiryToggle.isChecked()) + .putExtra(CardIOActivity.EXTRA_REQUIRE_CVV, mCvvToggle.isChecked()) + .putExtra(CardIOActivity.EXTRA_REQUIRE_POSTAL_CODE, mPostalCodeToggle.isChecked()) + .putExtra(CardIOActivity.EXTRA_RESTRICT_POSTAL_CODE_TO_NUMERIC_ONLY, mPostalCodeNumericOnlyToggle.isChecked()) + .putExtra(CardIOActivity.EXTRA_REQUIRE_CARDHOLDER_NAME, mCardholderNameToggle.isChecked()) + .putExtra(CardIOActivity.EXTRA_SUPPRESS_MANUAL_ENTRY, mSuppressManualToggle.isChecked()) + .putExtra(CardIOActivity.EXTRA_USE_CARDIO_LOGO, mUseCardIOLogoToggle.isChecked()) + .putExtra(CardIOActivity.EXTRA_LANGUAGE_OR_LOCALE, (String) mLanguageSpinner.getSelectedItem()) + .putExtra(CardIOActivity.EXTRA_USE_PAYPAL_ACTIONBAR_ICON, mShowPayPalActionBarIconToggle.isChecked()) + .putExtra(CardIOActivity.EXTRA_KEEP_APPLICATION_THEME, mKeepApplicationThemeToggle.isChecked()) + .putExtra(CardIOActivity.EXTRA_GUIDE_COLOR, Color.GREEN) + .putExtra(CardIOActivity.EXTRA_SUPPRESS_CONFIRMATION, mSuppressConfirmationToggle.isChecked()) + .putExtra(CardIOActivity.EXTRA_SUPPRESS_SCAN, mSuppressScanToggle.isChecked()) + .putExtra(CardIOActivity.EXTRA_RETURN_CARD_IMAGE, true); + + try { + int unblurDigits = Integer.parseInt(mUnblurEdit.getText().toString()); + intent.putExtra(CardIOActivity.EXTRA_UNBLUR_DIGITS, unblurDigits); + } catch(NumberFormatException ignored) {} + + startActivityForResult(intent, REQUEST_SCAN); + } + + public void onAutotest(View v) { + Log.i(TAG, "\n\n\n ============================== \n" + "successfully completed " + + numAutotestsPassed + " tests\n" + "beginning new test run\n"); + + Intent intent = new Intent(this, CardIOActivity.class) + .putExtra(CardIOActivity.EXTRA_REQUIRE_EXPIRY, false) + .putExtra(CardIOActivity.EXTRA_REQUIRE_CVV, false) + .putExtra(CardIOActivity.EXTRA_REQUIRE_POSTAL_CODE, false) + .putExtra(CardIOActivity.EXTRA_REQUIRE_CARDHOLDER_NAME, false) + .putExtra("debug_autoAcceptResult", true); + + startActivityForResult(intent, REQUEST_AUTOTEST); + + autotestMode = true; + } + + @Override + public void onStop() { + super.onStop(); + mResultLabel.setText(""); + Log.d(TAG, "onStop()"); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + Log.v(TAG, "onActivityResult(" + requestCode + ", " + resultCode + ", " + data + ")"); + + String outStr = new String(); + Bitmap cardTypeImage = null; + + if ((requestCode == REQUEST_SCAN || requestCode == REQUEST_AUTOTEST) && data != null + && data.hasExtra(CardIOActivity.EXTRA_SCAN_RESULT)) { + CreditCard result = data.getParcelableExtra(CardIOActivity.EXTRA_SCAN_RESULT); + if (result != null) { + outStr += "Card number: " + result.getRedactedCardNumber() + "\n"; + + CardType cardType = result.getCardType(); + cardTypeImage = cardType.imageBitmap(this); + outStr += "Card type: " + cardType.name() + " cardType.getDisplayName(null)=" + + cardType.getDisplayName(null) + "\n"; + + if (mEnableExpiryToggle.isChecked()) { + outStr += "Expiry: " + result.expiryMonth + "/" + result.expiryYear + "\n"; + } + + if (mCvvToggle.isChecked()) { + outStr += "CVV: " + result.cvv + "\n"; + } + + if (mPostalCodeToggle.isChecked()) { + outStr += "Postal Code: " + result.postalCode + "\n"; + } + + if (mCardholderNameToggle.isChecked()) { + outStr += "Cardholder Name: " + result.cardholderName + "\n"; + } + } + + if (autotestMode) { + numAutotestsPassed++; + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + onAutotest(null); + } + }, 500); + } + } else if (resultCode == Activity.RESULT_CANCELED) { + autotestMode = false; + } + + Bitmap card = CardIOActivity.getCapturedCardImage(data); + mResultImage.setImageBitmap(card); + mResultCardTypeImage.setImageBitmap(cardTypeImage); + + Log.i(TAG, "Set result: " + outStr); + mResultLabel.setText(outStr); + } + + private void setupLanguageList() { + List languages = new ArrayList<>(); + for (SupportedLocale locale : LocalizedStringsList.ALL_LOCALES) { + languages.add(locale.getName()); + } + + ArrayAdapter adapter = new ArrayAdapter<>(this, + android.R.layout.simple_dropdown_item_1line, languages); + mLanguageSpinner.setAdapter(adapter); + mLanguageSpinner.setSelection(adapter.getPosition("en")); + } +} diff --git a/SampleApp/src/main/java/io/card/development/SampleApplication.java b/SampleApp/src/main/java/io/card/development/SampleApplication.java new file mode 100644 index 0000000..8c56585 --- /dev/null +++ b/SampleApp/src/main/java/io/card/development/SampleApplication.java @@ -0,0 +1,12 @@ +package io.card.development; + +import android.app.Application; + +public class SampleApplication extends Application { + + @Override + public void onCreate() { + super.onCreate(); + DebugTools.setup(this); + } +} diff --git a/SampleApp/src/main/java/io/card/test/CardIOTestActivity.java b/SampleApp/src/main/java/io/card/test/CardIOTestActivity.java new file mode 100644 index 0000000..515f333 --- /dev/null +++ b/SampleApp/src/main/java/io/card/test/CardIOTestActivity.java @@ -0,0 +1,29 @@ +package io.card.test; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +import io.card.payment.CardIOActivity; + +public class CardIOTestActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent intent = new Intent(this, CardIOActivity.class) + .putExtra(CardIOActivity.EXTRA_SUPPRESS_CONFIRMATION, true) + .putExtra("io.card.payment.cameraBypassTestMode", true); + + startActivityForResult(intent, 1); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + setResult(resultCode, data); + finish(); + } +} diff --git a/SampleApp/src/main/java/org/my/scanExample/MyScanActivity.java b/SampleApp/src/main/java/org/my/scanExample/MyScanActivity.java deleted file mode 100644 index 507f87b..0000000 --- a/SampleApp/src/main/java/org/my/scanExample/MyScanActivity.java +++ /dev/null @@ -1,107 +0,0 @@ -package org.my.scanExample; - -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; -import android.view.View; -import android.widget.Button; -import android.widget.TextView; - -import io.card.payment.CardIOActivity; -import io.card.payment.CreditCard; - -public class MyScanActivity extends Activity { - final String TAG = getClass().getName(); - - private Button scanButton; - private TextView resultTextView; - - private int MY_SCAN_REQUEST_CODE = 100; // arbitrary int - - /** - * Called when the activity is first created. - */ - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.main); - - resultTextView = (TextView) findViewById(R.id.resultTextView); - scanButton = (Button) findViewById(R.id.scanButton); - - resultTextView.setText("card.io library version: " + CardIOActivity.sdkVersion() + "\nBuilt: " + CardIOActivity.sdkBuildDate()); - } - - @Override - protected void onResume() { - super.onResume(); - - if (CardIOActivity.canReadCardWithCamera()) { - scanButton.setText("Scan a credit card with card.io"); - } else { - scanButton.setText("Enter credit card information"); - } - } - - public void onScanPress(View v) { - // This method is set up as an onClick handler in the layout xml - // e.g. android:onClick="onScanPress" - - Intent scanIntent = new Intent(this, CardIOActivity.class); - - // customize these values to suit your needs. - scanIntent.putExtra(CardIOActivity.EXTRA_REQUIRE_EXPIRY, true); // default: false - scanIntent.putExtra(CardIOActivity.EXTRA_REQUIRE_CVV, false); // default: false - scanIntent.putExtra(CardIOActivity.EXTRA_REQUIRE_POSTAL_CODE, false); // default: false - scanIntent.putExtra(CardIOActivity.EXTRA_RESTRICT_POSTAL_CODE_TO_NUMERIC_ONLY, false); // default: false - scanIntent.putExtra(CardIOActivity.EXTRA_REQUIRE_CARDHOLDER_NAME, false); // default: false - - // hides the manual entry button - // if set, developers should provide their own manual entry mechanism in the app - scanIntent.putExtra(CardIOActivity.EXTRA_SUPPRESS_MANUAL_ENTRY, false); // default: false - - // matches the theme of your application - scanIntent.putExtra(CardIOActivity.EXTRA_KEEP_APPLICATION_THEME, false); // default: false - - // MY_SCAN_REQUEST_CODE is arbitrary and is only used within this activity. - startActivityForResult(scanIntent, MY_SCAN_REQUEST_CODE); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - String resultStr; - if (data != null && data.hasExtra(CardIOActivity.EXTRA_SCAN_RESULT)) { - CreditCard scanResult = data.getParcelableExtra(CardIOActivity.EXTRA_SCAN_RESULT); - - // Never log a raw card number. Avoid displaying it, but if necessary use getFormattedCardNumber() - resultStr = "Card Number: " + scanResult.getRedactedCardNumber() + "\n"; - - // Do something with the raw number, e.g.: - // myService.setCardNumber( scanResult.cardNumber ); - - if (scanResult.isExpiryValid()) { - resultStr += "Expiration Date: " + scanResult.expiryMonth + "/" + scanResult.expiryYear + "\n"; - } - - if (scanResult.cvv != null) { - // Never log or display a CVV - resultStr += "CVV has " + scanResult.cvv.length() + " digits.\n"; - } - - if (scanResult.postalCode != null) { - resultStr += "Postal Code: " + scanResult.postalCode + "\n"; - } - - if (scanResult.cardholderName != null) { - resultStr += "Cardholder Name : " + scanResult.cardholderName + "\n"; - } - } else { - resultStr = "Scan was canceled."; - } - resultTextView.setText(resultStr); - - } -} - diff --git a/SampleApp/src/main/res/layout/main.xml b/SampleApp/src/main/res/layout/main.xml deleted file mode 100644 index 51ca4f2..0000000 --- a/SampleApp/src/main/res/layout/main.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - -