diff --git a/app/build.gradle b/app/build.gradle index 4fd09ee..d6d7e11 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -26,8 +26,8 @@ android { applicationId "com.maxieds.chameleonminilivedebugger" minSdkVersion 21 targetSdkVersion 27 - versionCode 46 - versionName "0.4.6" + versionCode 47 + versionName "0.4.7" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" multiDexEnabled true } diff --git a/app/src/main/java/com/maxieds/chameleonminilivedebugger/ExportTools.java b/app/src/main/java/com/maxieds/chameleonminilivedebugger/ExportTools.java index cdc3c2a..22c405e 100644 --- a/app/src/main/java/com/maxieds/chameleonminilivedebugger/ExportTools.java +++ b/app/src/main/java/com/maxieds/chameleonminilivedebugger/ExportTools.java @@ -556,6 +556,12 @@ public static boolean saveBinaryDumpMFU(String filePathPrefix) { */ public static boolean cloneBinaryDumpMFU(byte[] dataBytes) { + if(dataBytes.length % 4 != 0) { // realign array: + int dbPadding = (4 - (dataBytes.length % 4)) % 4; + byte[] fullDataBytes = new byte[dataBytes.length + dbPadding]; + System.arraycopy(dataBytes, 0, fullDataBytes, 0, dataBytes.length); + dataBytes = fullDataBytes; + } ChameleonIO.executeChameleonMiniCommand(LiveLoggerActivity.serialPort, "CONFIG=MF_ULTRALIGHT", ChameleonIO.TIMEOUT); ChameleonIO.deviceStatus.updateAllStatusAndPost(true); for(int page = 0; page < dataBytes.length; page += 4) { @@ -566,7 +572,7 @@ public static boolean cloneBinaryDumpMFU(byte[] dataBytes) { (byte) (page / 4), // P2 }; byte[] apduSendBytesData = new byte[4]; - System.arraycopy(dataBytes, 4 * page, apduSendBytesData, 4, 4); + System.arraycopy(dataBytes, page, apduSendBytesData, 0, 4); int sendBytesHdr = Utils.bytes2Integer32(apduSendBytesHdr); int sendBytesData = Utils.bytes2Integer32(apduSendBytesData); String chameleonCmd = String.format("SEND %08x%08x", sendBytesHdr, sendBytesData); diff --git a/app/src/main/java/com/maxieds/chameleonminilivedebugger/LiveLoggerActivity.java b/app/src/main/java/com/maxieds/chameleonminilivedebugger/LiveLoggerActivity.java index b4deb93..ece181f 100644 --- a/app/src/main/java/com/maxieds/chameleonminilivedebugger/LiveLoggerActivity.java +++ b/app/src/main/java/com/maxieds/chameleonminilivedebugger/LiveLoggerActivity.java @@ -100,6 +100,7 @@ public class LiveLoggerActivity extends AppCompatActivity { public static List logDataEntries = new ArrayList(); public static int RECORDID = 0; public static boolean logDataFeedConfigured = false; + public static ScrollView logScrollView; public static SpinnerAdapter spinnerRButtonAdapter; public static SpinnerAdapter spinnerRButtonLongAdapter; public static SpinnerAdapter spinnerLButtonAdapter; @@ -137,6 +138,26 @@ public static void appendNewLog(LogEntryBase logEntry) { } logDataFeed.addView(logEntry.getLayoutContainer()); logDataEntries.add(logEntry); + if(logEntry instanceof LogEntryMetadataRecord) { // switch to the log tab to display the results: + Log.i(TAG, String.format("LogEntryMetaData record height: %d", logEntry.getLayoutContainer().getHeight())); + TabLayout tabLayout = (TabLayout) LiveLoggerActivity.runningActivity.findViewById(R.id.tab_layout); + tabLayout.getTabAt(TAB_LOG).select(); + if(logScrollView != null) { + logScrollView.postDelayed(new Runnable() { + @Override + public void run() { + ScrollView logScroller = (ScrollView) LiveLoggerActivity.runningActivity.findViewById(R.id.log_scroll_view); + //int bottomEltHeight = LiveLoggerActivity.logDataFeed.getChildAt(LiveLoggerActivity.logDataFeed.getChildCount() - 1).getHeight(); + LinearLayout lastLogElt = (LinearLayout) logDataFeed.getChildAt(logDataFeed.getChildCount() - 1); + lastLogElt.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + int bottomEltHeight = lastLogElt.getMeasuredHeight(); + Log.i(TAG, String.format("ScrollView bottom element height = %d + getBottom() = %d", bottomEltHeight, logScroller.getBottom())); + logScroller.scrollTo(0, logScroller.getBottom() + bottomEltHeight); + //logScroller.fullScroll(View.FOCUS_DOWN); + } + }, 250); + } + } } /** @@ -202,8 +223,8 @@ public void uncaughtException(Thread thread, Throwable ex) { protected void onCreate(Bundle savedInstanceState) { // fix bug where the tabs are blank when the application is relaunched: - super.onCreate(savedInstanceState); // should fix most of the crashes in the ANR report on Play Store if(runningActivity == null || !isTaskRoot()) { + super.onCreate(savedInstanceState); // should fix most of the crashes in the ANR report on Play Store if(!BuildConfig.DEBUG) { Log.w(TAG, "Loading crashlytics"); Fabric.with(this, new Crashlytics()); @@ -585,7 +606,11 @@ public static String getSettingFromDevice(UsbSerialDevice cmPort, String query, break; } } - return ChameleonIO.DEVICE_RESPONSE[0]; + String retValue = ChameleonIO.DEVICE_RESPONSE[0]; + if(retValue.equals("201:INVALID COMMAND USAGE")) { + retValue += " (Are you in READER mode?)"; + } + return retValue; } /** @@ -981,7 +1006,7 @@ else if(createCmd.equals("SEND") || createCmd.equals("SEND_RAW")) { appendNewLog(LogEntryMetadataRecord.createDefaultEventRecord("ERROR", "Input to send to card must be a _single hexadecimal_ byte!")); return; } - msgParam = "Card Response (if any): " + getSettingFromDevice(serialPort, createCmd + " " + bytesToSend); + msgParam = "Card Response (if any): \n" + getSettingFromDevice(serialPort, createCmd + " " + bytesToSend); } else if(createCmd.equals("AUTOCAL")) { msgParam = getSettingFromDevice(serialPort, "AUTOCALIBRATE"); @@ -1600,9 +1625,15 @@ else if(action.equals("RANDOM")) { public void actionButtonSendAPDU(View view) { String apduCmd = ApduUtils.apduTransceiveCmd.assembleAPDUString(); - String chameleonCmd = "SEND " + apduCmd; - String respData = getSettingFromDevice(serialPort, chameleonCmd); - String logMsg = String.format("Sent %s as %s ... \nResponse: %s", ApduUtils.apduTransceiveCmd.apduCmdDesc, apduCmd, respData); + String logMsg = String.format("Sent %s as %s ... \n", ApduUtils.apduTransceiveCmd.apduCmdDesc, apduCmd); + for(int b = 0; b < apduCmd.length(); b += 2) { + String apduByte = apduCmd.substring(b, b + 2); + String chameleonCmd = "SEND_RAW " + apduByte; + String respData = getSettingFromDevice(serialPort, chameleonCmd); + logMsg += String.format(" %s => Response: %s", apduByte, respData); + if (b + 2 < apduCmd.length()) + logMsg += "\n"; + } appendNewLog(LogEntryMetadataRecord.createDefaultEventRecord("APDU", logMsg)); } diff --git a/app/src/main/java/com/maxieds/chameleonminilivedebugger/TabFragment.java b/app/src/main/java/com/maxieds/chameleonminilivedebugger/TabFragment.java index 3ef5eac..2b9266f 100644 --- a/app/src/main/java/com/maxieds/chameleonminilivedebugger/TabFragment.java +++ b/app/src/main/java/com/maxieds/chameleonminilivedebugger/TabFragment.java @@ -26,8 +26,6 @@ import java.util.Locale; -import static android.content.ContentValues.TAG; - /** *

Tab Fragment

* Implements a Fragment for individual tab data in the application. @@ -37,6 +35,8 @@ */ public class TabFragment extends Fragment { + private static final String TAG = TabFragment.class.getSimpleName(); + /** * Definitions of the in-order tab indices. */ @@ -216,6 +216,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, LinearLayout logDataFeed = LiveLoggerActivity.logDataFeed; logDataFeed.setOrientation(LinearLayout.VERTICAL); logScroller.addView(logDataFeed); + logScroller.setFillViewport(true); + LiveLoggerActivity.logScrollView = logScroller; LiveLoggerActivity.logDataFeed = logDataFeed; LiveLoggerActivity.logDataFeedConfigured = true; } diff --git a/app/src/main/java/com/maxieds/chameleonminilivedebugger/Utils.java b/app/src/main/java/com/maxieds/chameleonminilivedebugger/Utils.java index b4597f5..83fdf70 100644 --- a/app/src/main/java/com/maxieds/chameleonminilivedebugger/Utils.java +++ b/app/src/main/java/com/maxieds/chameleonminilivedebugger/Utils.java @@ -261,11 +261,17 @@ public static int parseInt(String numberStr) { * @return Pretty String Format of the MFU tag */ public static String prettyPrintMFU(String mfuBytes) { - String pp = "PG | B0 B1 B2 B3 | LOCK AND/OR SPECIAL REGISTERS"; - pp += "================================================"; + String pp = " PG | B0 B1 B2 B3 | LOCK AND/OR SPECIAL REGISTERS\n"; + pp += "=================================================\n"; for(int page = 0; page < mfuBytes.length(); page += 8) { int pageNumber = page / 8; - byte[] pageData = Utils.hexString2Bytes(mfuBytes.substring(page, page + 8)); + Log.i(TAG, String.format("prettyPrintMFU: page#% 2d, page=% 2d", pageNumber, page)); + byte[] pageData = Utils.hexString2Bytes(mfuBytes.substring(page, Math.min(page + 8, mfuBytes.length()) - 1)); + if(pageData.length < 4) { + byte[] pageDataResized = new byte[4]; + System.arraycopy(pageData, 0, pageDataResized, 0, pageData.length); + pageData = pageDataResized; + } String specialRegs = ""; int lockBits = 0; if(pageNumber == 0) { @@ -300,7 +306,9 @@ else if(pageNumber == 19) { else { specialRegs = "ONE WAY CTRS"; } - String pageLine = String.format("% 2d | %02x %02x %02x %02x | [%s]\n", pageNumber, pageData[0], pageData[1], pageData[2], pageData[3]); + String pageLine = String.format(" % 2d | %02x %02x %02x %02x | [%s]", pageNumber, pageData[0], pageData[1], pageData[2], pageData[3], specialRegs); + if(page + 4 < mfuBytes.length()) + pageLine += "\n"; pp += pageLine; } return pp; diff --git a/app/src/main/res/layout/log_entry_ui.xml b/app/src/main/res/layout/log_entry_ui.xml index 6b7d7e4..f734bdd 100644 --- a/app/src/main/res/layout/log_entry_ui.xml +++ b/app/src/main/res/layout/log_entry_ui.xml @@ -5,7 +5,10 @@ style="@style/LogDataUITheme" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="vertical"> + android:layout_marginLeft="0.5dp" + android:layout_marginRight="0.5dp" + android:orientation="vertical" + > + + @@ -26,8 +31,9 @@ android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="?colorAccentLog" - android:textSize="12sp" - android:textStyle="normal" /> + android:textSize="10sp" + android:textStyle="normal" + android:typeface="monospace" />