org.ale.scanner.zotero.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for org.ale.scanner.zotero.MainActivity.java

Source

/** 
 * Copyright 2011 John M. Schanck
 * 
 * ScannerForZotero is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * ScannerForZotero is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with ScannerForZotero.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.ale.scanner.zotero;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Set;

import org.ale.scanner.zotero.PString;
import org.ale.scanner.zotero.data.Access;
import org.ale.scanner.zotero.data.Account;
import org.ale.scanner.zotero.data.BibItem;
import org.ale.scanner.zotero.data.BibItemDBHandler;
import org.ale.scanner.zotero.data.Database;
import org.ale.scanner.zotero.data.Group;
import org.ale.scanner.zotero.web.APIHandler;
import org.ale.scanner.zotero.web.googlebooks.GoogleBooksAPIClient;
import org.ale.scanner.zotero.web.worldcat.WorldCatAPIClient;
import org.ale.scanner.zotero.web.zotero.ZoteroAPIClient;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.ContentValues;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
import android.util.SparseArray;
import android.view.animation.Animation;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.animation.AnimationUtils;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AbsListView;
import android.widget.Button;
import android.widget.ExpandableListView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;

public class MainActivity extends Activity {

    //private static final String CLASS_TAG = MainActivity.class.getCanonicalName();

    private static final int RESULT_SCAN = 0;
    private static final int RESULT_EDIT = 1;

    private static final int UPLOAD_STATE_WAIT = 0;
    private static final int UPLOAD_STATE_PENDING = 1;
    private static final int UPLOAD_STATE_FAILURE = 2;

    private static final int SERVICE_GOOGLE = 0;
    private static final int SERVICE_WORLDCAT = 1;

    public static final String INTENT_EXTRA_ACCOUNT = "ACCOUNT";

    public static final String RC_PEND = "PENDING";
    public static final String RC_PEND_STAT = "STATUS";
    public static final String RC_CHECKED = "CHECKED";
    public static final String RC_ACCESS = "ACCESS";
    public static final String RC_GROUPS = "GROUPS";
    public static final String RC_UPLOADING = "UPLOADING";

    public static final String PREF_GROUP = "GROUP";
    public static final String PREF_SERVICE = "SERVICE";

    private ZoteroAPIClient mZAPI;
    private GoogleBooksAPIClient mGoogleBooksAPI;
    private WorldCatAPIClient mWorldCatAPI;

    private BibItemListAdapter mItemAdapter;

    private AlertDialog mAlertDialog = null;

    private ArrayList<String> mPendingItems;
    private ArrayList<Integer> mPendingStatus;
    private PendingListAdapter mPendingAdapter;
    private ListView mPendingList;

    private Animation[] mAnimations;

    private Account mAccount;

    private Access mAccountAccess;

    public Handler mUIThreadHandler;

    private int mUploadState;

    private SparseArray<PString> mGroups;
    private int mSelectedGroup;
    private int mISBNService;

    @Override
    public void onCreate(Bundle state) {
        super.onCreate(state);
        setContentView(R.layout.main);
        Bundle extras = getIntent().getExtras();

        mUIThreadHandler = new Handler();

        // Get the account we're logged in as
        mAccount = (Account) extras.getParcelable(INTENT_EXTRA_ACCOUNT);

        // Load preferences
        SharedPreferences prefs = getSharedPreferences(mAccount.getUid(), MODE_PRIVATE);
        // The group we'll upload to (default to user's personal library)
        mSelectedGroup = prefs.getInt(PREF_GROUP, Group.GROUP_LIBRARY);
        mISBNService = prefs.getInt(PREF_SERVICE, SERVICE_GOOGLE);

        // Initialize Clients
        mGoogleBooksAPI = new GoogleBooksAPIClient();
        mWorldCatAPI = new WorldCatAPIClient();
        mZAPI = new ZoteroAPIClient();
        mZAPI.setAccount(mAccount);

        // BibItem list
        ExpandableListView bibItemList = (ExpandableListView) findViewById(R.id.bib_items);

        // Pending item list
        View pendingListHolder = getLayoutInflater().inflate(R.layout.pending_item_list, bibItemList, false);
        bibItemList.addHeaderView(pendingListHolder);

        mPendingList = (ListView) pendingListHolder.findViewById(R.id.pending_item_list);

        int[] checked;
        if (state == null) { // Fresh activity
            mAccountAccess = null; // will check for permissions in onResume
            mPendingItems = new ArrayList<String>(2); // RC_PEND
            mPendingStatus = new ArrayList<Integer>(2); // RC_PEND_STAT
            checked = new int[0];
            mUploadState = UPLOAD_STATE_WAIT;
            mGroups = new SparseArray<PString>();
        } else { // Recreating activity
                 // Rebuild pending list
            mAccountAccess = state.getParcelable(RC_ACCESS);
            mPendingItems = state.getStringArrayList(RC_PEND);
            mPendingStatus = state.getIntegerArrayList(RC_PEND_STAT);
            // Set checked items
            checked = state.getIntArray(RC_CHECKED);

            mUploadState = state.getInt(RC_UPLOADING);
            mGroups = state.getSparseParcelableArray(RC_GROUPS);
        }

        // Initialize list adapters
        mItemAdapter = new BibItemListAdapter(MainActivity.this);
        mItemAdapter.setChecked(checked);
        bibItemList.setAdapter(mItemAdapter);
        registerForContextMenu(bibItemList);

        mPendingAdapter = new PendingListAdapter(MainActivity.this, R.layout.pending_item, R.id.pending_item_id,
                mPendingItems, mPendingStatus);
        mPendingList.setAdapter(mPendingAdapter);
        registerForContextMenu(mPendingList);

        // Listeners
        findViewById(R.id.scan_isbn).setOnClickListener(scanIsbn);
        findViewById(R.id.upload).setOnClickListener(uploadSelected);

        // Load animations
        mAnimations = new Animation[] { AnimationUtils.loadAnimation(MainActivity.this, R.anim.slide_in_next),
                AnimationUtils.loadAnimation(MainActivity.this, R.anim.slide_out_next),
                AnimationUtils.loadAnimation(MainActivity.this, R.anim.slide_in_previous),
                AnimationUtils.loadAnimation(MainActivity.this, R.anim.slide_out_previous) };

        // Upload Bar
        findViewById(R.id.upload_progress).setOnClickListener(dismissUploadStatus);
    }

    @Override
    public void onPause() {
        super.onPause();

        APIHandler.globalUnbindActivity();
        BibItemDBHandler bibdb = BibItemDBHandler.getInstance();
        bibdb.unbindAdapter();
        bibdb.removeMessages(BibItemListAdapter.FOUND_SAVED_ITEMS);

        if (mAlertDialog != null) {
            mAlertDialog.dismiss();
            mAlertDialog = null;
        }

        // Commit preferences
        SharedPreferences config = getSharedPreferences(mAccount.getUid(), MODE_PRIVATE);
        SharedPreferences.Editor editor = config.edit();
        editor.putInt(PREF_GROUP, mSelectedGroup);
        editor.putInt(PREF_SERVICE, mISBNService);
        editor.commit();
    }

    @Override
    public void onResume() {
        super.onResume();

        APIHandler.globalBindActivity(MainActivity.this);
        BibItemDBHandler.getInstance().bindAdapter(mItemAdapter);

        // FREE BEER: This is an ugly hack to prevent double-loading the
        // list, or failing to load it at all. I don't feel like coming up with
        // something more clever, so if you fix it I'll buy you a beer.
        if (mItemAdapter.getGroupCount() == 0) {
            mItemAdapter.fillFromDatabase(mAccount.getDbId());
        }

        // Show the correct upload bar
        if (mUploadState == UPLOAD_STATE_PENDING) {
            showUploadInProgress();
        } else {
            resetUploadStatus();
        }

        // Lookup account authorizations if necessary
        if (mAccountAccess == null && (Dialogs.displayedDialog != Dialogs.DIALOG_NO_PERMS)) {
            Dialogs.displayedDialog = Dialogs.DIALOG_CREDENTIALS;
        }

        // Draw the pending list
        int pendVis = mPendingAdapter.getCount() > 0 ? View.VISIBLE : View.GONE;
        mPendingList.setVisibility(pendVis);
        redrawPendingList();

        // Display any dialogs we were displaying before being destroyed
        switch (Dialogs.displayedDialog) {
        case (Dialogs.DIALOG_ZXING):
            mAlertDialog = Dialogs.getZxingScanner(MainActivity.this);
            break;
        case (Dialogs.DIALOG_CREDENTIALS):
            lookupAuthorizations();
            break;
        case (Dialogs.DIALOG_NO_PERMS):
            mAlertDialog = Dialogs.showNoPermissionsDialog(MainActivity.this);
            break;
        case (Dialogs.DIALOG_MANUAL_ENTRY):
            mAlertDialog = Dialogs.showManualEntryDialog(MainActivity.this);
            break;
        case (Dialogs.DIALOG_SELECT_LIBRARY):
            mAlertDialog = Dialogs.showSelectLibraryDialog(MainActivity.this, mGroups, mSelectedGroup);
            break;
        case (Dialogs.DIALOG_SEARCH_ENGINE):
            mAlertDialog = Dialogs.showSearchEngineDialog(MainActivity.this, mISBNService);
            break;
        }
    }

    public Account getUserAccount() {
        // hack for ZoteroHandler, which needs to know which user
        // is logged in so as to create Access objects. :/
        return mAccount;
    }

    @Override
    public void onSaveInstanceState(Bundle state) {
        super.onSaveInstanceState(state);
        state.putStringArrayList(RC_PEND, mPendingItems);
        state.putIntegerArrayList(RC_PEND_STAT, mPendingStatus);
        state.putIntArray(RC_CHECKED, mItemAdapter.getChecked());
        state.putParcelable(RC_ACCESS, mAccountAccess);
        state.putInt(RC_UPLOADING, mUploadState);
        state.putSparseParcelableArray(RC_GROUPS, mGroups);
    }

    public void postToUIThread(Runnable r) {
        mUIThreadHandler.post(r);
    }

    public void logout() {
        Intent intent = new Intent(MainActivity.this, LoginActivity.class);
        intent.putExtra(LoginActivity.INTENT_EXTRA_CLEAR_FIELDS, true);
        MainActivity.this.startActivity(intent);
        finish();
    }

    public void refreshPermissions() {
        mZAPI.getPermissions();
    }

    public void erasePermissions() {
        final int keyid = mAccount.getDbId();
        new Thread(new Runnable() {
            public void run() {
                getContentResolver().delete(Database.ACCESS_URI, Access.COL_ACCT + "=?",
                        new String[] { String.valueOf(keyid) });
            }
        }).start();
    }

    public void lookupAuthorizations() {
        final MainActivity parent = this;
        new Thread(new Runnable() {
            @Override
            public void run() {
                Cursor c = getContentResolver().query(Database.ACCESS_URI,
                        new String[] { Access.COL_GROUP, Access.COL_PERMISSION }, Access.COL_ACCT + "=?",
                        new String[] { String.valueOf(mAccount.getDbId()) }, null);
                if (c.getCount() == 0) { // Found no permissions
                    // Will call postAccountPermissions in ZoteroHandler if successful
                    postToUIThread(new Runnable() {
                        public void run() {
                            mAlertDialog = Dialogs.showCheckingCredentialsDialog(parent);
                        }
                    });
                    mZAPI.getPermissions();
                } else {
                    Access access = Access.fromCursor(c, mAccount.getDbId());
                    postAccountPermissions(access);
                }
                c.close();
            }
        }).start();
    }

    public void postAccountPermissions(final Access perms) {
        // Access perms is always returned from a background thread, so here
        // we save the permissions and launch new threads to fetch group titles
        // and collections.
        mUIThreadHandler.post(new Runnable() {
            public void run() {
                if (Dialogs.displayedDialog == Dialogs.DIALOG_CREDENTIALS) {
                    if (mAlertDialog != null)
                        mAlertDialog.dismiss();
                    Dialogs.displayedDialog = Dialogs.DIALOG_NO_DIALOG;
                }

                if (perms == null || !perms.canWrite()) {
                    // Tell the user they don't have sufficient permission
                    // and log them out
                    mAlertDialog = Dialogs.showNoPermissionsDialog(MainActivity.this);
                } else {
                    // User should be ready to go, lookup their groups and collections
                    // in the background.
                    mAccountAccess = perms;
                    loadGroups();
                }
            }
        });
    }

    public void loadGroups() {
        if (mAccountAccess.getGroupCount() == 0 && mAccountAccess.canWriteLibrary()) {
            mGroups = new SparseArray<PString>();
            mGroups.put(Group.GROUP_LIBRARY, new PString(getString(R.string.my_library)));
            return;
        }
        // Check that we have all the group titles
        new Thread(new Runnable() {
            public void run() {
                final SparseArray<PString> newGroupList = new SparseArray<PString>();
                Set<Integer> groups = mAccountAccess.getGroupIds();
                if (mAccountAccess.canWriteLibrary()) {
                    newGroupList.put(Group.GROUP_LIBRARY, new PString(getString(R.string.my_library)));
                }
                String[] selection = new String[] { TextUtils.join(",", groups) };
                Cursor c = getContentResolver().query(Database.GROUP_URI,
                        new String[] { Group._ID, Group.COL_TITLE }, Group._ID + " IN (?)", selection, null);

                // Figure out which groups we don't have
                c.moveToFirst();
                while (!c.isAfterLast()) {
                    int haveGroupId = c.getInt(0);
                    groups.remove(haveGroupId);
                    newGroupList.put(haveGroupId, new PString(c.getString(1)));
                    c.moveToNext();
                }
                c.close();
                // Update the group list
                mUIThreadHandler.post(new Runnable() {
                    public void run() {
                        mGroups = newGroupList;
                    }
                });
                // If we have any unknown groups, do a group lookup.
                if (groups.size() > 0) {
                    // Make new database entries for new groups. Mapping each
                    // id to "<Group ID>" temporarily.
                    ContentValues[] values = new ContentValues[groups.size()];
                    int i = 0;
                    for (Integer gid : groups) {
                        values[i] = new ContentValues();
                        values[i].put(Group._ID, gid);
                        values[i].put(Group.COL_TITLE, "<" + gid + ">");
                        i++;
                    }
                    getContentResolver().bulkInsert(Database.GROUP_URI, values);
                    mZAPI.getGroups();
                }
            }
        }).start();
    }

    public void bibFetchSuccess(final String isbn, final JSONObject info) {
        if (!mPendingAdapter.hasItem(isbn)) {
            // Item was deleted while pending
            return;
        }
        BibItem item = new BibItem(BibItem.TYPE_BOOK, info, mAccount.getDbId());
        mPendingAdapter.remove(isbn);
        if (mPendingAdapter.getCount() == 0) {
            mPendingList.setVisibility(View.GONE);
        }
        mItemAdapter.addItem(item);
        redrawPendingList();
    }

    public void bibFetchFailure(String isbn, Integer status) {
        mPendingAdapter.setStatus(isbn, status);
    }

    public void uploadSuccess(int[] dbrows) {
        mItemAdapter.setChecked(new int[0]);
        mItemAdapter.deleteItemsWithRowIds(dbrows);
        Toast.makeText(MainActivity.this, "Items added successfully", Toast.LENGTH_LONG).show();
        showUploadButton();
    }

    public void uploadFailure(Integer reason) {
        mUploadState = UPLOAD_STATE_FAILURE;
        ProgressBar prog = (ProgressBar) findViewById(R.id.upload_progress_bar);
        prog.setVisibility(View.GONE);

        TextView error = (TextView) findViewById(R.id.upload_error);
        error.setVisibility(View.VISIBLE);

        TextView output = (TextView) findViewById(R.id.upload_output);
        output.setText(getText(reason));
    }

    public void resetUploadStatus() {
        ProgressBar prog = (ProgressBar) findViewById(R.id.upload_progress_bar);
        prog.setVisibility(View.VISIBLE);

        TextView error = (TextView) findViewById(R.id.upload_error);
        error.setVisibility(View.GONE);

        TextView output = (TextView) findViewById(R.id.upload_output);
        output.setText(getText(ZoteroAPIClient.UPLOADING));
    }

    public void showUploadInProgress() {
        mUploadState = UPLOAD_STATE_PENDING;
        TextView output = (TextView) findViewById(R.id.upload_output);
        output.setText(getText(ZoteroAPIClient.UPLOADING));

        SafeViewFlipper vf = (SafeViewFlipper) findViewById(R.id.upload_flipper);
        if (vf.getCurrentView().getId() == R.id.upload) {
            vf.setInAnimation(mAnimations[0]); // Slide in previous
            vf.setOutAnimation(mAnimations[1]); // slide out previous
            vf.showNext();
        }
    }

    public void showUploadButton() {
        mUploadState = UPLOAD_STATE_WAIT;
        resetUploadStatus();
        SafeViewFlipper vf = (SafeViewFlipper) findViewById(R.id.upload_flipper);
        if (vf.getCurrentView().getId() == R.id.upload_progress) {
            vf.setInAnimation(mAnimations[2]); // slide in next
            vf.setOutAnimation(mAnimations[3]); // slide out next
            vf.showPrevious();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.options_menu_main, menu);
        return true;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);
        MenuItem check_all = (MenuItem) menu.findItem(R.id.ctx_check_all);

        int numChecked = mItemAdapter.getChecked().length;
        int groupCount = mItemAdapter.getGroupCount();

        String title = getString(R.string.check_all);
        // Set to "Uncheck All" if all are checked, or we're
        // waiting for db to return bibitems. In the latter case we
        // just assume all items are checked.
        if ((groupCount > 0 && numChecked == groupCount) || (groupCount == 0 && numChecked > 0)) {
            title = getString(R.string.uncheck_all);
        }

        check_all.setTitle(title);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle item selection
        switch (item.getItemId()) {
        case R.id.ctx_manual:
            mAlertDialog = Dialogs.showManualEntryDialog(MainActivity.this);
            break;
        case R.id.ctx_check_all:
            if (item.getTitle().equals(getString(R.string.uncheck_all))) {
                // Uncheck All
                mItemAdapter.setChecked(new int[0]);
            } else {
                // Check All
                int[] all = new int[mItemAdapter.getGroupCount()];
                for (int i = 0; i < all.length; i++)
                    all[i] = i;
                mItemAdapter.setChecked(all);
            }
            mItemAdapter.notifyDataSetChanged();
            break;
        case R.id.ctx_library:
            mAlertDialog = Dialogs.showSelectLibraryDialog(MainActivity.this, mGroups, mSelectedGroup);
            break;
        case R.id.ctx_engine:
            mAlertDialog = Dialogs.showSearchEngineDialog(MainActivity.this, mISBNService);
            break;
        case R.id.ctx_logout:
            logout();
            break;
        default:
            return super.onOptionsItemSelected(item);
        }
        return true;
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo);
        MenuInflater inflater = getMenuInflater();
        if (menuInfo instanceof ExpandableListContextMenuInfo) {
            ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuInfo;
            int type = ExpandableListView.getPackedPositionType(info.packedPosition);
            int group = ExpandableListView.getPackedPositionGroup(info.packedPosition);
            if (type != ExpandableListView.PACKED_POSITION_TYPE_NULL) {
                // It's not in the header
                inflater.inflate(R.menu.bib_item_context_menu, menu);
                menu.setHeaderTitle(mItemAdapter.getTitleOfGroup(group));
            }
        } else if (menuInfo instanceof AdapterContextMenuInfo) {
            if (v.getId() != R.id.pending_item_list) {
                AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
                inflater.inflate(R.menu.pending_item_context_menu, menu);
                menu.setHeaderTitle(mPendingAdapter.getItem(info.position));
            }
        }
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.ctx_edit:
            ExpandableListContextMenuInfo einfo = (ExpandableListContextMenuInfo) item.getMenuInfo();

            int index = (int) einfo.id;
            BibItem toEdit = (BibItem) mItemAdapter.getGroup(index);
            Intent intent = new Intent(MainActivity.this, EditItemActivity.class);
            intent.putExtra(EditItemActivity.INTENT_EXTRA_BIBITEM, toEdit);
            intent.putExtra(EditItemActivity.INTENT_EXTRA_INDEX, index);
            startActivityForResult(intent, RESULT_EDIT);
            break;
        case R.id.ctx_delete:
            ExpandableListContextMenuInfo dinfo = (ExpandableListContextMenuInfo) item.getMenuInfo();
            mItemAdapter.deleteItem((BibItem) mItemAdapter.getGroup((int) dinfo.id));
            break;
        case R.id.ctx_cancel:
            AdapterContextMenuInfo cinfo = (AdapterContextMenuInfo) item.getMenuInfo();

            mPendingAdapter.remove(mPendingAdapter.getItem(cinfo.position));
            if (mPendingAdapter.getCount() == 0)
                mPendingList.setVisibility(View.GONE);
            redrawPendingList();
            break;
        case R.id.ctx_retry:
            AdapterContextMenuInfo rinfo = (AdapterContextMenuInfo) item.getMenuInfo();
            String ident = mPendingAdapter.getItem(rinfo.position);
            if (mPendingAdapter.getStatus(ident) != PendingListAdapter.STATUS_LOADING) {
                mPendingAdapter.remove(ident);
                lookupISBN(ident);
            }
        default:
            return super.onContextItemSelected(item);
        }
        return true;
    }

    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
        switch (requestCode) {
        case RESULT_SCAN:
            if (resultCode == RESULT_OK) {
                String content = intent.getStringExtra("SCAN_RESULT"); // The scanned ISBN
                String format = intent.getStringExtra("SCAN_RESULT_FORMAT"); // "EAN 13"
                handleBarcode(content, format);
            }
            break;
        case RESULT_EDIT:
            if (resultCode == RESULT_OK) {
                Bundle extras = intent.getExtras();
                BibItem replacement = extras.getParcelable(EditItemActivity.INTENT_EXTRA_BIBITEM);
                mItemAdapter.replaceItem(replacement);
            }
            break;
        }
    }

    protected void addToPendingList(String content) {
        if (mPendingAdapter.hasItem(content)) {
            Toast.makeText(MainActivity.this, "This item is already in your list.", Toast.LENGTH_LONG).show();
        } else {
            mPendingAdapter.add(content);
            mPendingList.setVisibility(View.VISIBLE);
            redrawPendingList();
        }
    }

    protected void handleBarcode(String content, String format) {
        switch (Util.parseBarcode(content, format)) {
        case (Util.SCAN_PARSE_ISBN):
            lookupISBN(content);
            break;
        case (Util.SCAN_PARSE_ISSN):
            if (content.length() == 13) {
                content = Util.eanToISSN(content);
            }
            lookupISSN(content);
            break;
        default:
            addToPendingList(content);
            mPendingAdapter.setStatus(content, PendingListAdapter.STATUS_UNKNOWN_TYPE);
            break;
        }
        redrawPendingList();
    }

    protected void lookupISBN(String isbn) {
        addToPendingList(isbn);
        switch (mISBNService) {
        case SERVICE_GOOGLE:
            mGoogleBooksAPI.isbnLookup(isbn);
            break;
        case SERVICE_WORLDCAT:
            mWorldCatAPI.isbnLookup(isbn);
            break;
        }
    }

    protected void lookupISSN(String issn) {
        addToPendingList(issn);
        //switch(mISBNService){
        //case SERVICE_GOOGLE:
        //    mGoogleBooksAPI.isbnLookup(isbn);
        //    break;
        //case SERVICE_WORLDCAT:
        mWorldCatAPI.issnLookup(issn);
        //    break;
        //}
    }

    protected void setISBNService(int sid) {
        mISBNService = sid;
    }

    protected void setSelectedGroup(int gid) {
        mSelectedGroup = gid;
    }

    private final Button.OnClickListener scanIsbn = new Button.OnClickListener() {
        public void onClick(View v) {
            try {
                Intent intent = new Intent(getString(R.string.zxing_intent_scan));
                intent.setPackage(getString(R.string.zxing_pkg));
                intent.putExtra("SCAN_MODE", "ONE_D_MODE");
                startActivityForResult(intent, RESULT_SCAN);
            } catch (ActivityNotFoundException e) {
                // Ask the user if we should install ZXing scanner
                Dialogs.getZxingScanner(MainActivity.this);
            }
        }
    };

    private final Button.OnClickListener uploadSelected = new Button.OnClickListener() {
        public void onClick(View v) {
            int[] checked = mItemAdapter.getChecked();
            if (checked.length == 0) {
                Toast.makeText(MainActivity.this, "No items selected", Toast.LENGTH_LONG).show();
                return;
            }

            int dest = (mSelectedGroup == Group.GROUP_LIBRARY) ? Integer.parseInt(mAccount.getUid())
                    : mSelectedGroup;

            int[] rows = new int[Math.min(checked.length, ZoteroAPIClient.MAX_UPLOAD_CNT)];
            JSONObject items = new JSONObject();
            try {
                items.put("items", new JSONArray());
                int row_indx = 0;
                for (int b = 0; b < checked.length; b++) {
                    BibItem bib = (BibItem) mItemAdapter.getGroup(checked[b]);
                    rows[row_indx] = bib.getId();
                    items.accumulate("items", bib.getSelectedInfo());
                    row_indx++;
                    if (row_indx == ZoteroAPIClient.MAX_UPLOAD_CNT) {
                        // Upload the batch
                        mZAPI.addItems(items, rows, dest);
                        showUploadInProgress();
                        row_indx = 0;
                        items = new JSONObject();
                    }
                }
                // As long as the batch size isn't perfectly divisible by 50, we
                // need to upload the remaining items
                if (checked.length % 50 != 0) {
                    mZAPI.addItems(items, rows, dest);
                    showUploadInProgress();
                }
            } catch (JSONException e) {
                // TODO Prompt about failure
                e.printStackTrace();
                // Clear the selection
                mItemAdapter.setChecked(new int[0]);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
    };

    private final Button.OnClickListener dismissUploadStatus = new Button.OnClickListener() {
        public void onClick(View v) {
            if (mUploadState == UPLOAD_STATE_FAILURE) {
                showUploadButton();
            }
        }
    };

    private void redrawPendingList() {
        /* Pretty terrible hack, Android doesn't like my ListView inside a
         * relative layout as a header in an expandable list view. Go figure.
         * It wasn't getting the height of said element correctly. */
        int count = mPendingAdapter.getCount();
        RelativeLayout r = ((RelativeLayout) findViewById(R.id.pending_item_holder));
        AbsListView.LayoutParams params = (AbsListView.LayoutParams) r.getLayoutParams();
        params.height = count * mPendingAdapter._hack_childSize;
        r.setLayoutParams(params);
    }
}