Thursday, October 9, 2014

20 steps to Java/DLL integration over JNI

I'm not well-versed in C++ nor .Net, so I was still learning as I encountered this at work. While not being overly generic yet not too specific about the ordeal I encountered, it involved getting a DLL to work with our Java components. Sounds straightforward enough? It was... kind of. Guy 1 is in charge of developing the main DLL and its compilation stuff. Guy 2 is versed in C++ but understands Java sufficiently to provide the DLL for the Java Native Interface (JNI) adapter to the main DLL from Guy 1, along with basic testing classes in Java.

It certainly looked like he had already done all my work for me. Not quite. I'd have to proceed with his setup to get their bits working with the rest of our web application in a Java framework. Here's how it went... (WARNING: wall of text that includes technical stuff and bitching)

TL;DR - integrated Java with DLL using JNI, shenanigans with co-workers ensue, Java calls to DLL success.

Step 1: Setup the DLL paths and environment provided by Guy 2 in my Eclipse,

Step 2: Realise Guy 2 provided basic XML parsing via DocumentBuilder,

Step 3: Convert everything he's done into XStream-friendly POJO,

Step 4: Realise XStream is an external library and commence search for alternative to reduce additional footprint,

Step 5: Took the advice of Blaise Doughan and adopted Java Architecture for XML Binding (JAXB) because it's part of the javax.xml.bind package,

Step 6: Discovered the joys of source annotations and XML adapters,

Step 7: Realise that the XML definition that was long decided before I was brought on board (and already implemented on the .Net server side) is complicated (e.g.:
<fans>
  <fan type="standing"/>
  <fan type="desk"/>
  <fan type="celebrity"/>
</fans>
) by attribute naming that prevented JAXB from unmarshalling the String to object,

Step 8: Came up with my own convoluted (I swear it's as elegant as I could get it) solution to first read the <fans> to determine what types were in the list provided, before manually stripping <fans> from the String front and back and running it through JAXB another time, (if you have a better way, let me know!)

Step 9: Attempt to run the whole shebang with the DLL,

Step 10: Act surprised that JNI complained of an UnsatisfiedLinkError for the DLL,

Step 11: Figured that Eclipse had to be told where to look with -Djava.library.path="C:/that_dumb_place/Dlls" when running my test program,

Step 12: Nope still won't work, spend the morning poking around some more before giving up and asking Guy 2 if I'd missed out anything,

Step 13: Guy 2 refers me to Guy 1 for the DLL portion, "fair enough" I lied to myself,

Step 14: Decided I should test further without the Java components by first running the .exe test program they provided... crashed,

Step 15: Approach Guy 1 who tells me he'd investigate the error on his end first, while I proceed to lunch,

Step 16: Guy 1 returns to offer me his solution of running his test program on his spare laptop that's been configured to work bug-free, instead of my problematic laptop that's long been tainted with other muck from previous projects,

Step 17: "Does this thing have Java installed?", I queried. Guy 1immediately forbids me from running my codes on his precious laptop. Explained to him I needed to run my codes with his because I had to integrate them for this project. Seeing there was no other way to squirm out of this, he promptly took over my laptop to figure out how to get his test program running,

Step 18: I twirled around in my chair for almost 2 hours before he returned triumphant. It worked! At last, his test program works on my laptop. He had installed a new driver that goes along with a new model of the Human-Interface Device (HID) that we have already provided the client.

Step 19: Indeed, System.load("That_Darn_JNI") works with the DLL from Guy 2 in his codes that I've since adopted and turned it from a baby into an adolescent, complete with full-blown set of beans and action classes, just as long Eclipse knows about that -Djava.library.path it cleverly denies knowing about,

Step 20: Try running my test program. Nope, Unsatisfied remains. Made comparison between JNI DLL and existing known working DLL from another product, and finally getting around to rectify minor bugs in my codes to marshal/unmarshal between the XML and POJO.

So I finally got what I wanted... well almost. The following addendum is the cherry missing from my cake...

Step 21: Realised that because I'd shifted the DLL away from the original XYZ.Product package into my Java-convention style of com.XYZ.asia.product.client caused the JNI to not map to the native DLL, and that it'd be confusing to leave the product package as XYZ.Product while the rest of the project is differently identified,

Step 22: Ask Guy 2 about the JNI, he patiently explained about how the header works but ultimately suggested that I should talk to Guy 1,

Step 23: Locate Guy 1 about passing him the JNI header to generate the DLL for me. Nope, his bunch of codes would take him way longer to generate the resulting DLL,

Step 24: Return to Guy 2 about it. He relents, and I generate the JNI header via javah.exe mapping to the relocated JNI wrapper

Step 25: I have yet to test this new DLL yet, although I reckon it should function as expected... and hopefully this tiring liaison with them is over for the time being...

And thus, at the end of the day, I got my DLL the way I wanted it, the HID works on my machine, the client-server calls are working and I'm getting the XML strings parsed correctly into Java objects. It's been gruelling, but educational.

To be continued...

Saturday, January 18, 2014

Android ShareActionProvider does not provide share action without shareHistoryFileName set

TL;DR - It is compulsory to call setShareHistoryFileName() after initialising from getActionProvider().


I was trying to call the ShareActionProvider from a Contextual Action Bar. As it turned out, after trying to figure out why it didn't work on my Jelly Bean emulator, I found this issue that has had hardly any attention on it. Running the built-in ApiDemo (for API 10, on both the emulator and my actual device) proved that it really doesn't have anything catered for this action. Bottomline is, I had to switch to plugging in my own phone to test my change for this bit.

When the documentation mentioned "Defaults to DEFAULT_SHARE_HISTORY_FILE_NAME." my assumption was that I did not need to even call setShareHistoryFileName() since it's already set with that as default. Why shouldn't it? And without which, my share action fails to react, even when the icon is sitting nicely on the screen.

I only managed to find people asking how to disable the share history, but not this other bit. I don't really care for the share history in this case, but getting it to work without which seems impossible. Dear people at Google, maybe would you like to consider either stating that setShareHistoryFileName() must be called, to pass in the DEFAULT_SHARE_HISTORY_FILE_NAME, or maybe just plain set it in by default or to null if it was never set? Isn't that what built-in defaults are for?

Even though I know there are tons of codes floating around that's basically similar every which way, here's a snippet of my codes for posterity:
private ActionMode.Callback actionModeCallBack = new ActionMode.Callback() { ...
        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            MenuInflater inflater = mode.getMenuInflater();
            inflater.inflate(R.menu.confirmed_context_menu, menu);
           
            shareAction = (ShareActionProvider) menu.findItem(R.id.share_confirmed).getActionProvider();
            shareAction.setShareHistoryFileName(ShareActionProvider.DEFAULT_SHARE_HISTORY_FILE_NAME);
            shareAction.setShareIntent(createShareIntent());
            return true;
        }
 I must qualify my lack of understanding was specifically for this bit of use for the ShareActionProvider in a contextual action bar that appears only after a long-press, but no where did I spot anybody mentioning that they succeeded in calling the Provider without setting the share history... or is everyone just copying the codes from documentation line-for-line?

Friday, January 17, 2014

Closing the Contextual Action Bar when switching to another Fragment

Been playing around in Android in my spare time when I encountered this... I have 3 Fragments listed in tab mode on the action bar:
  1. Fragment #3 is displayed, and has a contextual action bar shown from a long-press.
  2. Selecting Fragment #2 brings me to the page, but with the contextual action bar left over from Fragment #3.
 Clearly at this stage, I'd want the contextual action bar gone, but the results from Google were inconclusive. Fortunately, a bit of random checking in the documentation turned up onDestroy() in the Fragment class. There were hints and speculation suggesting the use of hide()/finish()/invalidate() and possibly applying them in the main Activity, from StackOverflow but I didn't find them exactly useful to my situation.

By chance, I decided to give one combination a shot, gave it a whirl and it seems to be working for me. It's surprisingly straightforward.

Here's my actionMode and its callBack:
    private ActionMode actionMode = null;
    private ActionMode.Callback actionModeCallBack = new ActionMode.Callback() {
       
        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            return false;
        }
       
        @Override
        public void onDestroyActionMode(ActionMode mode) {
            try {
                for(int i=0;i<getListView().getCount();i++) {
                    getListView().setItemChecked(i, false);
                }
                updateHighlightsOnChecked();
                getListView().setChoiceMode(ListView.CHOICE_MODE_NONE);
            } catch(IllegalStateException e) {
                Log.d(app_name_tag, "ConfirmedFragment.actionModeCallBack.onDestroyActionMode IllegalStateException");
            }
            mode = null;
        }
       
        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            MenuInflater inflater = mode.getMenuInflater();
            inflater.inflate(R.menu.confirmed_context_menu, menu);
            return true;
        }
       
        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            List<Integer> itemPositionList = new ArrayList<Integer>();
            Log.d(app_name_tag, "checked items: " + getListView().getCheckedItemCount());
            SparseBooleanArray bArr = getListView().getCheckedItemPositions();
            if(bArr != null) {
                for(int i=0;i<getListView().getCount();i++) {
                    if(bArr.get(i)) {
                        itemPositionList.add(i);
                    }
                }
            }
           
            switch(item.getItemId()) {
                case R.id.remove_confirmed:
                    callback.onConfirmedOrderRemoved(itemPositionList);
                    updateOrderList();
                    mode.finish();
                    return true;
            }
           
            return false;
        }
    };

The actionMode gets initiated in the long-click listener:
@Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
       
        getListView().setOnItemLongClickListener(new OnItemLongClickListener() {
           
            @Override
            public boolean onItemLongClick(AdapterView<?> adapterView, View view, int position, long id) {
                getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
                getListView().setItemChecked(position, true);
                updateHighlightsOnChecked();
                actionMode = getActivity().startActionMode(actionModeCallBack);
                view.setSelected(true);
                return true;
            }
        });
    }

And lastly, the most important part:

    @Override
    public void onDestroy() {
        super.onDestroy();
        if(actionMode != null)
            actionMode.finish();
    }

I figured since the contextual action bar (or pretty much everything else) would be wiped when the fragment gets swapped out, it can give the actionMode a holler to finish its business.

Give it a try and let me know if it works out for you.