Skip to content

Commit

Permalink
add dump file viewer
Browse files Browse the repository at this point in the history
  • Loading branch information
cinit committed Dec 16, 2021
1 parent 137d994 commit 9751e22
Show file tree
Hide file tree
Showing 18 changed files with 1,496 additions and 117 deletions.
24 changes: 23 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,29 @@

<activity
android:name="cc.ioctl.nfcdevicehost.activity.MipmapGenActivity"
android:exported="true" />
android:exported="true"
android:launchMode="singleTop" />

<activity
android:name="cc.ioctl.nfcdevicehost.activity.JumpEntryActivity"
android:exported="true"
android:theme="@style/Theme.AppDefault.Translucent">
<intent-filter android:label="@string/intent_label_view_hal_dump">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />

<data android:scheme="file" />
<data android:scheme="content" />
<data android:mimeType="application/*" />
<data android:pathPattern=".*.haldump" />
<data android:pathPattern=".*.HALDUMP" />
<data android:pathPattern=".*..*.haldump" />
</intent-filter>
</activity>

<activity
android:name="cc.ioctl.nfcdevicehost.activity.SidebandHostActivity"
android:exported="false" />

<service
android:name="cc.ioctl.nfcdevicehost.service.CardEmuCtrlSvc"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package cc.ioctl.nfcdevicehost.activity;

import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;

import androidx.annotation.Nullable;

/**
* External file share/browser springboard entry point.
* This activity is short-lived and only used to launch a target activity.
* This is a transient Activity, and will be finished immediately after launching the target activity.
*/
public class JumpEntryActivity extends BaseActivity {

@Override
protected boolean doOnCreate(@Nullable Bundle savedInstanceState) {
Intent intent = getIntent();
String action = intent.getAction();
if (Intent.ACTION_VIEW.equals(action)) {
Intent targetIntent = new Intent(intent);
targetIntent.setComponent(new ComponentName(this, SidebandHostActivity.class));
startActivity(targetIntent);
}
finish();
return false;
}

@Override
protected boolean shouldRetainActivitySavedInstanceState() {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class MainUiFragmentActivity extends BaseActivity {
@Override
protected boolean doOnCreate(Bundle savedInstanceState) {
super.doOnCreate(savedInstanceState);
setContentView(R.layout.activity_dashboard);
setContentView(R.layout.activity_main_ui_host);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
DrawerLayout drawer = findViewById(R.id.drawer_layout);
Expand Down Expand Up @@ -81,8 +81,8 @@ public NavController getNavController() {
public void requestRootToStartDaemon() {
// show a dialog while we are waiting for root
final AlertDialog requestDialog = new AlertDialog.Builder(this)
.setTitle("Requesting root")
.setMessage("Please grant root permission")
.setTitle(R.string.ui_dialog_title_requesting_root)
.setMessage(R.string.ui_dialog_msg_body_requesting_root)
.show();
// try request root permission
ThreadManager.execute(() -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package cc.ioctl.nfcdevicehost.activity;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;

import cc.ioctl.nfcdevicehost.R;
import cc.ioctl.nfcdevicehost.activity.ui.dump.HalDumpFileViewFragment;

/**
* Fragment host activity for the secondary fragments, eg. saved dump viewer.
*/
public class SidebandHostActivity extends BaseActivity {

@Override
protected boolean doOnCreate(@Nullable Bundle savedInstanceState) {
super.doOnCreate(savedInstanceState);
setContentView(R.layout.activity_sideband_host);
if (savedInstanceState == null) {
Intent intent = getIntent();
String action = intent.getAction();
if (Intent.ACTION_VIEW.equals(action)) {
Uri uri = intent.getData();
Bundle args = new Bundle();
args.putString(HalDumpFileViewFragment.EXTRA_CONTENT, uri.toString());
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.sideband_host_fragment_container, HalDumpFileViewFragment.class, args)
.commit();
}
}
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
return true;
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.menu_activity_main_ui_common, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_exit) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package cc.ioctl.nfcdevicehost.activity.ui.dump;

import android.view.LayoutInflater;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;

import java.util.Date;
import java.util.Locale;

import cc.ioctl.nfcdevicehost.decoder.NciPacketDecoder;
import cc.ioctl.nfcdevicehost.decoder.NxpHalV2EventTranslator;
import cc.ioctl.nfcdevicehost.util.ByteUtils;

public abstract class BaseHalDumpFragment extends Fragment {

public static class NciDumpViewHolder extends RecyclerView.ViewHolder {
public enum ViewType {
TRANSACTION,
IOCTL
}

public ViewType type;
public cc.ioctl.nfcdevicehost.databinding.ItemMainNciDumpBinding binding;

public NciDumpViewHolder(cc.ioctl.nfcdevicehost.databinding.ItemMainNciDumpBinding binding, ViewType type) {
super(binding.getRoot());
this.type = type;
this.binding = binding;
}
}

protected void updateListViewItem(@NonNull NciDumpViewHolder holder,
@NonNull NxpHalV2EventTranslator.TransactionEvent event) {
long timestamp = event.timestamp;
String seqTime;
Date now = new Date();
Date seqTimeDate = new Date(timestamp);
if (now.getYear() == seqTimeDate.getYear() && now.getMonth() == seqTimeDate.getMonth()
&& now.getDate() == seqTimeDate.getDate()) {
// the same day, HH:mm:ss.SSS
seqTime = String.format(Locale.ROOT, "#%d ", event.sequence)
+ String.format(Locale.ROOT, "%1$tH:%1$tM:%1$tS.%1$tL", event.timestamp);
} else {
// not the same day, yyyy-MM-dd HH:mm:ss.SSS
seqTime = String.format(Locale.ROOT, "#%d ", event.sequence)
+ String.format(Locale.ROOT, "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL", event.timestamp);
}
String typeText = "<INVALID>";
StringBuilder dataText = new StringBuilder();
if (event instanceof NxpHalV2EventTranslator.IoctlTransactionEvent) {
typeText = "IOCTL";
NxpHalV2EventTranslator.IoctlTransactionEvent ev = (NxpHalV2EventTranslator.IoctlTransactionEvent) event;
dataText.append("request: 0x").append(Integer.toHexString(ev.request));
dataText.append(" arg: 0x").append(Long.toHexString(ev.arg));
} else if (event instanceof NxpHalV2EventTranslator.RawTransactionEvent) {
typeText = "???";
NxpHalV2EventTranslator.RawTransactionEvent ev = (NxpHalV2EventTranslator.RawTransactionEvent) event;
dataText.append(ev.direction);
dataText.append("\n");
dataText.append(ByteUtils.bytesToHexString(ev.data));
} else if (event instanceof NxpHalV2EventTranslator.NciTransactionEvent) {
NxpHalV2EventTranslator.NciTransactionEvent ev = (NxpHalV2EventTranslator.NciTransactionEvent) event;
switch (ev.type) {
case NCI_DATA: {
NciPacketDecoder.NciDataPacket pk = (NciPacketDecoder.NciDataPacket) ev.packet;
typeText = "DAT";
dataText.append("conn: ").append(pk.connId).append(" ").append("credits: ").append(pk.credits);
dataText.append("\npayload(").append(pk.data.length).append("): \n");
dataText.append(ByteUtils.bytesToHexString(pk.data));
break;
}
case NCI_NTF:
case NCI_RSP:
case NCI_CMD: {
NciPacketDecoder.NciControlPacket pk = (NciPacketDecoder.NciControlPacket) ev.packet;
typeText = (pk.type == NciPacketDecoder.Type.NCI_CMD) ? "CMD"
: ((pk.type == NciPacketDecoder.Type.NCI_NTF) ? "NTF" : "RSP");
int msgType = pk.type.getInt();
String name = NciPacketDecoder.getNciOperationName(msgType, pk.groupId, pk.opcodeId);
if (name == null) {
name = "Unknown";
if (NciPacketDecoder.isNciOperationProprietary(msgType, pk.groupId, pk.opcodeId)) {
name += " Proprietary";
}
name += " " + String.format(Locale.ROOT, "GID:0x%02X", pk.groupId)
+ " " + String.format(Locale.ROOT, "OID:0x%02X", pk.opcodeId);
} else {
name += " (" + String.format(Locale.ROOT, "0x%02X", pk.groupId)
+ "/" + String.format(Locale.ROOT, "0x%02X", pk.opcodeId) + ")";
}
dataText.append(name);
dataText.append("\npayload(").append(pk.data.length).append(")\n");
dataText.append(ByteUtils.bytesToHexString(pk.data));
break;
}
default: {
typeText = "<UNKNOWN>";
dataText.append(ev.packet.toString());
}
}
}
holder.binding.textViewItemNciDumpTime.setText(seqTime);
holder.binding.textViewItemNciDumpType.setText(typeText);
holder.binding.textViewItemNciDumpMessage.setText(dataText.toString());
}

public abstract static class AbsNciDumpAdapter extends RecyclerView.Adapter<NciDumpViewHolder> {
@NonNull
@Override
public NciDumpViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
cc.ioctl.nfcdevicehost.databinding.ItemMainNciDumpBinding binding = cc.ioctl.nfcdevicehost.databinding.ItemMainNciDumpBinding.inflate(LayoutInflater.from(parent.getContext()),
parent, false);
return new NciDumpViewHolder(binding, NciDumpViewHolder.ViewType.TRANSACTION);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package cc.ioctl.nfcdevicehost.activity.ui.dump;

import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

import cc.ioctl.nfcdevicehost.R;
import cc.ioctl.nfcdevicehost.activity.MainUiFragmentActivity;
import cc.ioctl.nfcdevicehost.daemon.INciHostDaemon;
import cc.ioctl.nfcdevicehost.databinding.FragmentMainDumpBinding;
import cc.ioctl.nfcdevicehost.decoder.NxpHalV2EventTranslator;
import cc.ioctl.nfcdevicehost.util.ThreadManager;

/**
* Fragment for showing a dump file.
* <p>
* Host Activity may be {@link MainUiFragmentActivity}
* or {@link cc.ioctl.nfcdevicehost.activity.SidebandHostActivity}
*/
public class HalDumpFileViewFragment extends BaseHalDumpFragment {

public static final String EXTRA_CONTENT = HalDumpFileViewFragment.class.getName() + ".EXTRA_CONTENT";

private String mRawSerializedDumpData = null;
private ArrayList<INciHostDaemon.IoEventPacket> mRawIoEvents = null;
private ArrayList<NxpHalV2EventTranslator.TransactionEvent> mTransactionEvents = null;

private FragmentMainDumpBinding mBinding;
private final AbsNciDumpAdapter mDumpAdapter = new AbsNciDumpAdapter() {
@Override
public void onBindViewHolder(@NonNull NciDumpViewHolder holder, int position) {
NxpHalV2EventTranslator.TransactionEvent event = mTransactionEvents.get(position);
updateListViewItem(holder, event);
}

@Override
public int getItemCount() {
return mTransactionEvents == null ? 0 : mTransactionEvents.size();
}
};

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
Context context = inflater.getContext();
mBinding = FragmentMainDumpBinding.inflate(inflater, container, false);
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
RecyclerView recyclerView = mBinding.recyclerViewMainFragmentDumpList;
recyclerView.setLayoutManager(layoutManager);
layoutManager.setOrientation(RecyclerView.VERTICAL);
recyclerView.addItemDecoration(new DividerItemDecoration(context, DividerItemDecoration.VERTICAL));
recyclerView.setItemAnimator(new DefaultItemAnimator());
mBinding.recyclerViewMainFragmentDumpList.setAdapter(mDumpAdapter);
// load data file
Bundle args = getArguments();
final String uriPath = args == null ? null : args.getString(EXTRA_CONTENT);
if (uriPath == null) {
requireActivity().finish();
return null;
}
Uri uri = Uri.parse(uriPath);
final ContentResolver resolver = requireContext().getContentResolver();
ThreadManager.async(() -> {
try {
InputStream is = resolver.openInputStream(uri);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) > 0) {
baos.write(buffer, 0, len);
}
is.close();
mRawSerializedDumpData = baos.toString();
try {
mRawIoEvents = NxpHalV2EventTranslator.loadIoEventPacketsFromString(mRawSerializedDumpData);
NxpHalV2EventTranslator translator = new NxpHalV2EventTranslator();
translator.pushBackRawIoEvents(mRawIoEvents);
mTransactionEvents = translator.getTransactionEvents();
ThreadManager.runOnUiThread(() -> mDumpAdapter.notifyDataSetChanged());
} catch (RuntimeException e) {
ThreadManager.runOnUiThread(() -> new AlertDialog.Builder(context)
.setTitle(R.string.ui_dialog_error_title)
.setMessage(e.toString())
.setCancelable(false)
.setPositiveButton(android.R.string.ok, (dialog1, which) -> requireActivity().finish())
.show());
}
} catch (IOException e) {
ThreadManager.runOnUiThread(() -> new AlertDialog.Builder(context)
.setTitle(R.string.ui_dialog_error_title)
.setMessage(context.getString(R.string.ui_dialog_unable_to_open_file_v0s, uri.toString())
+ "\n" + e)
.setCancelable(false)
.setPositiveButton(android.R.string.ok, (dialog1, which) -> requireActivity().finish())
.show());
}
});
return mBinding.getRoot();
}

@Override
public void onResume() {
super.onResume();
Activity activity = requireActivity();
activity.setTitle(R.string.ui_title_main_dump);
if (activity instanceof MainUiFragmentActivity) {
((MainUiFragmentActivity) activity).hideFloatingActionButton();
}

}
}
Loading

0 comments on commit 9751e22

Please sign in to comment.