Thursday, January 30, 2014

Contextual Action Bar with Android's ExpandableListView

I have put together a sample project on Github illustrating that the Contextual Action Bar pattern can be applied to Android's ExpandableListView, if your use case implies that you either want to select groups or the childs and do not need a mixed selection.

The magic is quite simple, it mainly consists in storing the type of selection in the ActionMode.Callback's onItemCheckedStateChanged() method:

    @Override
    public void onItemCheckedStateChanged(ActionMode mode, int position,
                                          long id, boolean checked) {
      int count = lv.getCheckedItemCount();
      if (count == 1) {
        expandableListSelectionType = ExpandableListView.getPackedPositionType(
            lv.getExpandableListPosition(position));
      }
      mode.setTitle(String.valueOf(count));
      configureMenu(mode.getMenu(), count);
    }

Then you need to implement onChildClick() and onGroupClick() listeners that check the list items once action mode is active:

    @Override
    public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
      if (mActionMode != null)  {
        if (expandableListSelectionType == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
          int flatPosition = parent.getFlatListPosition(ExpandableListView.getPackedPositionForGroup(groupPosition));
          parent.setItemChecked(
              flatPosition,
              !parent.isItemChecked(flatPosition));
          return true;
        }
      }
      return false;
    }
    @Override
    public boolean onChildClick(ExpandableListView parent, View v,
        int groupPosition, int childPosition, long id) {
      if (mActionMode != null)  {
        if (expandableListSelectionType == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
          int flatPosition = parent.getFlatListPosition(
              ExpandableListView.getPackedPositionForChild(groupPosition,childPosition));
          parent.setItemChecked(
              flatPosition,
              !parent.isItemChecked(flatPosition));
        }
        return true;
      }
      return false;
    }

The sample project also illustrates a technique of defining the context actions as part of four groups, depending on two combined criteria:
  • actions for groups or children
  • bulk actions that can be applied to multiple items, and single item actions

Sunday, November 24, 2013

Android Calendar Provider Instances Query on Legacy systems

I have run into a strange problem, when querying the Instances URI of Androids Calendar Provider on older Android versions. I was trying to get event instances from a given calendar (with id and planerCalendardId) and restrict to instances whose begin date is between two values, but the following query stubbornly refused to return any results:

    String[] INSTANCE_PROJECTION = new String[] {
          Instances.EVENT_ID,
          Instances._ID
        };
      Uri.Builder eventsUriBuilder = CalendarContractCompat.Instances.CONTENT_URI
          .buildUpon();
      ContentUris.appendId(eventsUriBuilder, lastExecutionTimeStamp);
      ContentUris.appendId(eventsUriBuilder, now);
      Uri eventsUri = eventsUriBuilder.build();
      //Instances.Content_URI returns events that fall totally or partially in a given range
      //we additionally select only instances where the begin is inside the range
      //because we want to deal with each instance only once
      Cursor cursor = getContentResolver().query(eventsUri, INSTANCE_PROJECTION,
          Events.CALENDAR_ID + " = ? AND "+ Instances.BEGIN + " BETWEEN ? AND ?",
          new String[]{
            planerCalendarId,
            String.valueOf(lastExecutionTimeStamp),
            String.valueOf(now)},
            null);

After banging my head for some time in different directions, I found out that only inspecting the source makes the behaviour understandable. Older versions of the Calendar provider, incorrectly handle the selection arguments passed in, in fact they throw them away. As can be seen in http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android-apps/2.2_r1.1/com/android/providers/calendar/CalendarProvider2.java on lines 660,661, the selectionArgs parameter to the query method is simply not passed to handleInstanceQuery.
The problem can be worked around by merging the selection arguments into the selection.

      Cursor cursor = getContentResolver().query(eventsUri, INSTANCE_PROJECTION,
          Events.CALENDAR_ID + " = " + planerCalendarId + " AND "+ Instances.BEGIN +
              " BETWEEN " + lastExecutionTimeStamp + " AND " + now,
          null,
          null);

Saturday, November 9, 2013

Open bitcoin URLs in firefox with Multibit (Ubuntu 12.10 with Gnome-Shell)

Having created Bitcoin donation URLS for my OpenSource projects MyExpenses and SendFithFtp, I tried to find out how to make Firefox open the bitcoin URLs with Multibit.
I am running Ubuntu 12.10. with Gnome Shell, and after some experiments, the following worked:
Multibit creates a menu entry at ~/.local/share/applications/MultiBit\ 0.5.14-1382603328396.desktop

Modify the file to make it handle URLs:

[Desktop Entry]
Categories=
Comment=MultiBit 0.5.14
Comment[de]=MultiBit 0.5.14
Encoding=UTF-8
Exec=/usr/bin/java -jar /home/michael/MultiBit-0.5.14/multibit-exe.jar %u
GenericName=
GenericName[de]=
Icon=/home/michael/MultiBit-0.5.14/multibit48.png
MimeType=x-scheme-handler/bitcoin
Name=MultiBit 0.5.14
Name[de]=MultiBit 0.5.14
Path=/home/michael/MultiBit-0.5.14
ServiceTypes=
SwallowExec=
SwallowTitle=
Terminal=
TerminalOptions=
Type=Application
URL=
X-KDE-SubstituteUID=false
X-KDE-Username=root

and register it with:

xdg-desktop-menu install --novendor ~/.local/share/applications/MultiBit\ 0.5.14-1382603328396.desktop

When clicking in Firefox on a link to a Bitcoin URL, and you get prompted for selecting an application to open the URL with, select /usr/open/xdg-open .

Multibit unfortunatley only handles the URL correctly, when it is already running.

Saturday, July 6, 2013

DialogFragment displaying options from a cursor held by the Activity

Continuing to chase down performance problems with Android's StrictMode in My Expenses: On several occasions, I needed to present the user a dialog for selecting an item from a cursor. Previously I was loading this cursor in the fragment without passing through a CursorLoader. Instead of loading the cursor again, if it is already held in the activity, I was looking for a way to access the activity's cursor, and came up with SelectFromCursorDialogFragment. It defines an interface (SelectFromCursorDialogListener) that the activity displaying the dialog implements in order to 1) provide the cursor (getCursor) and 2) to process the result (onItemSelected).

An example use case is the "Move transaction" command, that allows to move a transaction from one account to another, and needs to present a dialog for selecting the target account. Following code is used for displaying the dialog

      args = new Bundle();
      args.putInt("id", R.id.MOVE_TRANSACTION_COMMAND);
      args.putString("dialogTitle",getString(R.string.dialog_title_select_account));
      args.putString("column", KEY_LABEL);
      args.putLong("contextTransactionId",info.id);
      args.putInt("cursorId", ACCOUNTS_OTHER_CURSOR);
      SelectFromCursorDialogFragment.newInstance(args)
        .show(getSupportFragmentManager(), "SELECT_ACCOUNT");

SelectFromCursorDialogFragment receives a bundle with the following required keys
id
Allows to identify the context from which the dialog is opened in the onItemSelected callback.
dialogTitle
The title for the dialog.
column
the column being used for the options to be selected.
cursorId
the id under which the cursor will be made available in getCursor.
 
The bundle is handed back in the callback, so any additional information can be added that is needed for processing, in this example we store as "contextTransactionId", the transaction that the user wants to move.
In this example, we implement the interface in the following way

  @Override
  public void onItemSelected(Bundle args) {
    switch(args.getInt("id")) {
    case R.id.MOVE_TRANSACTION_COMMAND:
      Transaction.move(
          args.getLong("contextTransactionId"),
          args.getLong("result"));
      break;
    }
  }
  @Override
  public Cursor getCursor(int cursorId) {
    switch(cursorId) {
    case ACCOUNTS_OTHER_CURSOR:
      return new AllButOneCursorWrapper(mAccountsCursor,currentPosition);
    }
    return null;
  }

The "result" key in the bundle is where the selected item's id is stored.
The cursor returned for the id ACCOUNTS_OTHER_CURSOR is the solution to another challenge. The activity has a cursor for all accounts, but the target obviously needs to be a different account from the current one, so we need to exclude one row from the cursor, which is handled by the AllButOneCursorWrapper class.

EDIT: It turned out, that above solution only worked as long, as the user does not change orientation while displaying the dialog. To make the dialog react correctly to orientation changes proofed to be quite complicated. The problem is, that when the activity is recreated and the fragment instantiated, the cursor is not yet available, and you need to hook into the cursor loader's callback in order to call up the fragment and set the cursor there. SelectFromCursorDialogFragment now implements this requirement through the following changes:
  1. Instead of using the builder's setSingleChoiceItems method, an SimpleCursorAdapter is created and stored as class member variable.
  2. SelectFromCursorDialogFragment defines a setCursor method which uses the adapter's swapCursor method.
  3. the getCursor method of the SelectFromCursorDialogListener interface has a second argument tag which allows the activity to store the tag of the fragment, if it does not have the cursor the fragment needs.
  4. in the activity's onLoadFinished method, we check if there is fragment tag stored as a callback, retrieve the fragment, and use its  setCursor method.
Some overhead for a tiny detail, but worthwhile, if you pay attention, as I try to do, to have your app handle orientation changes correctly.

Thursday, July 4, 2013

Configuring Actionbar from a Fragment using CursorLoader inside a ViewPager

My Expenses allows to swipe through accounts using a ViewPager.  with a FragmentPagerAdapter, i.e, each list of transactions has its own fragment.
The actionbar has a "Reset" menu item, that should be disabled/invisible in case the account whose page is visible has no transactions. Until now, this was done in the activity's onPrepareOptionsMenu method by a database call that retrieved the row count for the current account. Not surprisingly, this call got marked as performance impeding, when I evaluated the app's performance with StrictMode.

I thought it would be easy to have the fragment take care of configuring the menu item, since it has the cursor, and knows if it has any transactions to display. Nevertheless, it took me some time to find out the working solution. First, you need to take care of the fact that the ViewPager instantiates the fragments before they become visible, so you need a mechanism, to trigger the reconfiguration when the fragment becomes visible.
I was misled by the assumption that I would have to either overwrite or use the fragment's setMenuVisibility method, but in fact this method is already called by the adapter with its argument menuVisible as true, when the page becomes visible, and it takes care of invalidating the options menu.

Having understood this, the solution becomes quite simple and can be seen in this commit.

First in the cursor loaders callback, we store in a boolean field hasItems the information if the cursor contains any rows. And if we are dealing with the fragment that is currently visible, we invalidate the options menu. This second step takes care of situations where the cursor is loaded again, when a transaction is added or deleted. I am using the methods provided by ActionBarSherlock, but the solution should work the same with the equivalent standard Android API methods.

   public void onLoadFinished(Loader arg0, Cursor c) {
     mAdapter.swapCursor(c);
     hasItems = c.getCount()>0;
     if (isVisible())
       getSherlockActivity().supportInvalidateOptionsMenu();
   }
 
   @Override
   public void onLoaderReset(Loader arg0) {
     mAdapter.swapCursor(null);
     hasItems = false;
     if (isVisible())
       getSherlockActivity().supportInvalidateOptionsMenu();
   }
 
Second, we use hasItems field in onPrepareOptionsMenu, which now is triggered in both situations, we are interested in: first when the visibility of a fragment changes, second when the cursor for the visible fragment is reloaded. 

  @Override
  public void onPrepareOptionsMenu(Menu menu) {
    if (isVisible())
      menu.findItem(R.id.RESET_ACCOUNT_COMMAND).setVisible(hasItems);
  } 

Finally, we need to call

      setHasOptionsMenu(true); 

in onCreate() in order to make sure that onPrepareOptionsMenu is called on our fragment. It does not matter that the menu item is not added by the fragment, but by the activity. The fragment is allowed to manipulate the menu created by the activity.

Sunday, December 9, 2012

Moving from jquery tabs and iframes to jekyll

For the MyExpenses web site, I had used Jquery Tabs for navigating between the different sections, and contents that were also displayed from inside the app, like the user manual and the news were displayed in iframes. That worked well, but needed considerable tweaking through Javascript for
  • being able to link to individual sections and their subpages
  • resizing the iframe dynamically
It worked well, until I wanted to integrate Disqus commenting to the News which did not fit into the iframe where the News were displayed.
While looking for a solution, I discovered that Github pages, where the website is hosted, supports Jekyll, which is a mechanism for generating static pages from templates.
With the Jekyll, the navigation is now stored in one template, that is considerably simpler and easier to maintain than the Javascript code used before, and each time I push content to Github, the site is regenerated from this template. Manual and news are still generated from Docbook through XSLT stylesheets, but those output now the YAML header that Jekyll interprets.
The new interface now uses vertical tabs (the design needs to be polished, I admit), that make the whole site display better on the smartphone, and I no longer need a special navigation-less in-app display.
The added advantage: MyExpenses now can call Android's browser for displaying content and no longer relies on its own Webview, thus no longer needs the permission to access the Internet, which understandably is a stumbling block for security sensitive users when dealing with a financial application.

Saturday, September 15, 2012

D-Link DWA-525 A2 on Ubuntu 12.04

Trying to make a newly bought D-Link Wireless N 150 PCI Desktop Adapter DWA-525 A2 work under Ubuntu 12.04 (precise). References like this blog post that refer to drivers from the Ralinktech website do not work for the A2 version of the device. You can find out, what version you have through

lspci |grep -i ralink

which for the A2 should give:

05:03.0 Network controller: Ralink corp. Device 5360 

Wikidevi references a patch to the rt2800pci module (part of the rt2x00 driver) but I found no explanation on how to apply this to a Ubuntu 12.04, and it took some time to find out. The patch seems to apply to Kernel 3.4, but Precise uses 3.2. Ubuntuforums has a thread on the topic and one posting explains how to patch a download of compat-wireless for kernel 2.6 manually.

Instead of going that route, I wanted to give the patch for the 3.4. version a try, and on the way document a solution that would be easier to reproduce. The compat-wireless releases can be found at http://wireless.kernel.org/en/users/Download/stable/ , the patch is linked from http://rt2x00.serialmonkey.com/pipermail/users_rt2x00.serialmonkey.com/2012-May/004942.html . Putting that together:

wget http://rt2x00.serialmonkey.com/pipermail/users_rt2x00.serialmonkey.com/attachments/20120507/e2072201/attachment.bin
wget http://www.orbit-lab.org/kernel/compat-wireless-3-stable/v3.4/compat-wireless-3.4-rc3-1.tar.bz2
tar -xjf compat-wireless-3.4-rc3-1.tar.bz2
cd  compat-wireless-3.4-rc3-1
patch -p1 <../attachment.bin
./scripts/driver-select rt2x00
make
sudo make install
sudo make unload
sudo modprobe rt2800pci

With this, the card should be recognized and start to work. I suspect that the compilation of the module has to be redone each time, the kernel is upgraded.