From d7a4b2ba7b88a12741da03ba1e1318ba73501d41 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Sun, 17 Aug 2014 15:01:32 -0400 Subject: [PATCH] Add a tab in Enabler to view received CAN messages. Half of #152, need to add a detail view. --- .../res/layout/can_message_list_fragment.xml | 29 ++++++ enabler/res/layout/can_message_list_item.xml | 32 ++++++ .../com/openxc/enabler/CanMessageAdapter.java | 84 ++++++++++++++++ .../enabler/CanMessageViewFragment.java | 98 +++++++++++++++++++ .../openxc/enabler/OpenXcEnablerActivity.java | 7 +- .../openxc/enabler/ThreadPreconditions.java | 14 +++ .../src/com/openxc/messages/CanMessage.java | 19 +++- 7 files changed, 279 insertions(+), 4 deletions(-) create mode 100644 enabler/res/layout/can_message_list_fragment.xml create mode 100644 enabler/res/layout/can_message_list_item.xml create mode 100644 enabler/src/com/openxc/enabler/CanMessageAdapter.java create mode 100644 enabler/src/com/openxc/enabler/CanMessageViewFragment.java create mode 100644 enabler/src/com/openxc/enabler/ThreadPreconditions.java diff --git a/enabler/res/layout/can_message_list_fragment.xml b/enabler/res/layout/can_message_list_fragment.xml new file mode 100644 index 000000000..e73eece93 --- /dev/null +++ b/enabler/res/layout/can_message_list_fragment.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + diff --git a/enabler/res/layout/can_message_list_item.xml b/enabler/res/layout/can_message_list_item.xml new file mode 100644 index 000000000..dba8f2de2 --- /dev/null +++ b/enabler/res/layout/can_message_list_item.xml @@ -0,0 +1,32 @@ + + + + + + + + + + diff --git a/enabler/src/com/openxc/enabler/CanMessageAdapter.java b/enabler/src/com/openxc/enabler/CanMessageAdapter.java new file mode 100644 index 000000000..85f3a68b7 --- /dev/null +++ b/enabler/src/com/openxc/enabler/CanMessageAdapter.java @@ -0,0 +1,84 @@ +package com.openxc.enabler; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import com.openxc.messages.CanMessage; + +public class CanMessageAdapter extends BaseAdapter { + final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); + + private Map mMessages; + private List mValues; + private Context mContext; + + public CanMessageAdapter(Context context) { + mContext = context; + mMessages = new LinkedHashMap<>(); + } + + public void add(CanMessage message) { + ThreadPreconditions.checkOnMainThread(); + mMessages.put(message.getId(), message); + mValues = new ArrayList<>(mMessages.values()); + Collections.sort(mValues); + notifyDataSetChanged(); + } + + @Override + public int getCount() { + return mMessages.size(); + } + + @Override + public CanMessage getItem(int position) { + return mValues.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if(convertView == null) { + convertView = LayoutInflater.from(mContext) + .inflate(R.layout.can_message_list_item, parent, false); + } + + CanMessage message = getItem(position); + + TextView busView = (TextView) convertView.findViewById(R.id.can_message_bus); + busView.setText("" + message.getBus()); + + TextView idView = (TextView) convertView.findViewById(R.id.can_message_id); + idView.setText("0x" + Integer.toHexString(message.getId())); + + TextView dataView = (TextView) convertView.findViewById(R.id.can_message_data); + dataView.setText("0x" + bytesToHex(message.getData())); + + return convertView; + } + + public static String bytesToHex(byte[] bytes) { + char[] hexChars = new char[bytes.length * 2]; + for ( int j = 0; j < bytes.length; j++ ) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); +} +} diff --git a/enabler/src/com/openxc/enabler/CanMessageViewFragment.java b/enabler/src/com/openxc/enabler/CanMessageViewFragment.java new file mode 100644 index 000000000..f18a9ffb6 --- /dev/null +++ b/enabler/src/com/openxc/enabler/CanMessageViewFragment.java @@ -0,0 +1,98 @@ +package com.openxc.enabler; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.support.v4.app.ListFragment; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; + +import com.openxc.VehicleManager; +import com.openxc.messages.CanMessage; +import com.openxc.messages.KeyMatcher; +import com.openxc.messages.VehicleMessage; + +public class CanMessageViewFragment extends ListFragment { + private static String TAG = "CanMessageView"; + + private VehicleManager mVehicleManager; + private CanMessageAdapter mAdapter; + + private VehicleMessage.Listener mListener = new VehicleMessage.Listener() { + @Override + public void receive(final VehicleMessage message) { + if(message instanceof CanMessage) { + getActivity().runOnUiThread(new Runnable() { + public void run() { + mAdapter.add(message.asCanMessage()); + } + }); + } + } + }; + + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, + IBinder service) { + Log.i(TAG, "Bound to VehicleManager"); + mVehicleManager = ((VehicleManager.VehicleBinder)service + ).getService(); + + // TODO would be nice to be able to register to receive a specific + // type, sorta like we do with Measurement - + // addListener(CanMessage.class, listener), for example + mVehicleManager.addListener(KeyMatcher.getWildcardMatcher(), + mListener); + } + + public void onServiceDisconnected(ComponentName className) { + Log.w(TAG, "VehicleService disconnected unexpectedly"); + mVehicleManager = null; + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mAdapter = new CanMessageAdapter(getActivity()); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + View v = inflater.inflate(R.layout.can_message_list_fragment, + container, false); + return v; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + setListAdapter(mAdapter); + } + + @Override + public void onResume() { + super.onResume(); + getActivity().bindService( + new Intent(getActivity(), VehicleManager.class), + mConnection, Context.BIND_AUTO_CREATE); + } + + @Override + public void onPause() { + super.onPause(); + if(mVehicleManager != null) { + Log.i(TAG, "Unbinding from vehicle service"); + getActivity().unbindService(mConnection); + mVehicleManager = null; + } + } +} diff --git a/enabler/src/com/openxc/enabler/OpenXcEnablerActivity.java b/enabler/src/com/openxc/enabler/OpenXcEnablerActivity.java index ac16a8393..504791ab2 100644 --- a/enabler/src/com/openxc/enabler/OpenXcEnablerActivity.java +++ b/enabler/src/com/openxc/enabler/OpenXcEnablerActivity.java @@ -42,7 +42,6 @@ public class OpenXcEnablerActivity extends FragmentActivity { private static String TAG = "OpenXcEnablerActivity"; - static final int NUM_TABS = 2; private EnablerFragmentAdapter mAdapter; private ViewPager mPager; @@ -87,7 +86,7 @@ public boolean onCreateOptionsMenu(Menu menu) { } public static class EnablerFragmentAdapter extends FragmentPagerAdapter { - private static final String[] mTitles = { "Status", "Dashboard" }; + private static final String[] mTitles = { "Status", "Dashboard", "CAN" }; public EnablerFragmentAdapter(FragmentManager fm) { super(fm); @@ -95,7 +94,7 @@ public EnablerFragmentAdapter(FragmentManager fm) { @Override public int getCount() { - return NUM_TABS; + return mTitles.length; } @Override @@ -109,6 +108,8 @@ public Fragment getItem(int position) { return new StatusFragment(); } else if(position == 1) { return new VehicleDashboardFragment(); + } else if(position == 2) { + return new CanMessageViewFragment(); } return new StatusFragment(); } diff --git a/enabler/src/com/openxc/enabler/ThreadPreconditions.java b/enabler/src/com/openxc/enabler/ThreadPreconditions.java new file mode 100644 index 000000000..be02d6c9e --- /dev/null +++ b/enabler/src/com/openxc/enabler/ThreadPreconditions.java @@ -0,0 +1,14 @@ +package com.openxc.enabler; + +import android.os.Looper; + +public class ThreadPreconditions { + public static void checkOnMainThread() { + if (BuildConfig.DEBUG) { + if (Thread.currentThread() != Looper.getMainLooper().getThread()) { + throw new IllegalStateException("This method should be called from the Main Thread"); + } + } + } +} + diff --git a/openxc/src/com/openxc/messages/CanMessage.java b/openxc/src/com/openxc/messages/CanMessage.java index 95cf664df..36ebf799a 100644 --- a/openxc/src/com/openxc/messages/CanMessage.java +++ b/openxc/src/com/openxc/messages/CanMessage.java @@ -10,7 +10,7 @@ import com.google.common.base.Objects; import com.google.gson.annotations.SerializedName; -public class CanMessage extends KeyedMessage { +public class CanMessage extends KeyedMessage implements Comparable { public static final String ID_KEY = "id"; public static final String BUS_KEY = "bus"; public static final String DATA_KEY = "data"; @@ -64,6 +64,23 @@ public static boolean containsRequiredFields(Set fields) { return fields.containsAll(sRequiredFields); } + @Override + public int compareTo(CanMessage other) { + if(getBus() < other.getBus()) { + return -1; + } else if(getBus() > other.getBus()) { + return 1; + } else { + if(getId() < other.getId()) { + return -1; + } else if(getId() > other.getId()) { + return 1; + } + } + return 0; + } + + @Override public boolean equals(Object obj) { if(!super.equals(obj) || !(obj instanceof CanMessage)) {