org.openintents.shopping.ui.ShoppingActivity.java Source code

Java tutorial

Introduction

Here is the source code for org.openintents.shopping.ui.ShoppingActivity.java

Source

/* 
 * Copyright (C) 2007-2010 OpenIntents.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.openintents.shopping.ui;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;

import junit.framework.Assert;

import org.apache.http.message.BasicNameValuePair;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.openintents.OpenIntents;
import org.openintents.distribution.DistributionLibraryFragmentActivity;
import org.openintents.distribution.DownloadOIAppDialog;
import org.openintents.intents.GeneralIntents;
import org.openintents.intents.ShoppingListIntents;
import org.openintents.provider.Alert;
import org.openintents.provider.Location.Locations;
import org.openintents.shopping.LogConstants;
import org.openintents.shopping.R;
import org.openintents.shopping.library.provider.ShoppingContract;
import org.openintents.shopping.library.provider.ShoppingContract.Contains;
import org.openintents.shopping.library.provider.ShoppingContract.ContainsFull;
import org.openintents.shopping.library.provider.ShoppingContract.Items;
import org.openintents.shopping.library.provider.ShoppingContract.Lists;
import org.openintents.shopping.library.provider.ShoppingContract.Status;
import org.openintents.shopping.library.provider.ShoppingContract.Stores;
import org.openintents.shopping.library.util.PriceConverter;
import org.openintents.shopping.library.util.ShoppingUtils;
import org.openintents.shopping.share.Connectivity;
import org.openintents.shopping.share.Connectivity.JsonProcessor;
import org.openintents.shopping.ui.dialog.DialogActionListener;
import org.openintents.shopping.ui.dialog.EditItemDialog;
import org.openintents.shopping.ui.dialog.EditItemDialog.FieldType;
import org.openintents.shopping.ui.dialog.EditItemDialog.OnItemChangedListener;
import org.openintents.shopping.ui.dialog.NewListDialog;
import org.openintents.shopping.ui.dialog.RenameListDialog;
import org.openintents.shopping.ui.dialog.ThemeDialog;
import org.openintents.shopping.ui.dialog.ThemeDialog.ThemeDialogListener;
import org.openintents.shopping.ui.tablet.ShoppingListFilterFragment;
import org.openintents.shopping.ui.widget.QuickSelectMenu;
import org.openintents.shopping.ui.widget.ShoppingItemsView;
import org.openintents.shopping.ui.widget.ShoppingItemsView.ActionBarListener;
import org.openintents.shopping.ui.widget.ShoppingItemsView.DragListener;
import org.openintents.shopping.ui.widget.ShoppingItemsView.DropListener;
import org.openintents.shopping.ui.widget.ShoppingItemsView.OnCustomClickListener;
import org.openintents.shopping.widgets.CheckItemsWidget;
import org.openintents.util.MenuIntentOptionsWithIcons;
import org.openintents.util.ShakeSensorListener;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.ContentObserver;
import android.database.Cursor;
import android.hardware.SensorManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v2.os.Build;
import android.support.v2.view.MenuCompat;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.CursorAdapter;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;
import com.nullwire.trace.ExceptionHandler;

/**
 * 
 * Displays a shopping list.
 * 
 */
public class ShoppingActivity extends DistributionLibraryFragmentActivity
        implements ThemeDialogListener, OnCustomClickListener, ActionBarListener, OnItemChangedListener { // implements
    // AdapterView.OnItemClickListener
    // {

    /**
     * TAG for logging.
     */
    private static final String TAG = "ShoppingActivity";
    private static final boolean debug = false || LogConstants.debug;

    public class MyGestureDetector extends SimpleOnGestureListener {
        private static final float DISTANCE_DIP = 16.0f;
        private static final float PATH_DIP = 40.0f;
        // convert dip measurements to pixels
        final float scale = getResources().getDisplayMetrics().density;
        int scaledDistance = (int) (DISTANCE_DIP * scale + 0.5f);
        int scaledPath = (int) (PATH_DIP * scale + 0.5f);

        // For more information about touch gestures and screens support, see:
        // http://developer.android.com/resources/articles/gestures.html
        // http://developer.android.com/reference/android/gesture/package-summary.html
        // http://developer.android.com/guide/practices/screens_support.html try
        // {

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            if (e1 == null || e2 == null)
                return false;

            try {
                DisplayMetrics dm = getResources().getDisplayMetrics();

                int REL_SWIPE_MIN_DISTANCE = (int) (SWIPE_MIN_DISTANCE * dm.densityDpi / 160.0f);
                int REL_SWIPE_MAX_OFF_PATH = (int) (SWIPE_MAX_OFF_PATH * dm.densityDpi / 160.0f);
                int REL_SWIPE_THRESHOLD_VELOCITY = (int) (SWIPE_THRESHOLD_VELOCITY * dm.densityDpi / 160.0f);

                if (Math.abs(e1.getY() - e2.getY()) > REL_SWIPE_MAX_OFF_PATH)
                    return false;
                // right to left swipe
                if (e1.getX() - e2.getX() > REL_SWIPE_MIN_DISTANCE
                        && Math.abs(velocityX) > REL_SWIPE_THRESHOLD_VELOCITY) {
                    Toast.makeText(ShoppingActivity.this, "Left Swipe", Toast.LENGTH_SHORT).show();
                    changeList(-1);
                } else if (e2.getX() - e1.getX() > REL_SWIPE_MIN_DISTANCE
                        && Math.abs(velocityX) > REL_SWIPE_THRESHOLD_VELOCITY) {
                    Toast.makeText(ShoppingActivity.this, "Right Swipe", Toast.LENGTH_SHORT).show();
                    changeList(1);
                }
            } catch (Exception e) {
                // nothing
            }
            return false;
        }

    }

    private static final int SWIPE_MIN_DISTANCE = 120;
    private static final int SWIPE_MAX_OFF_PATH = 250;
    private static final int SWIPE_THRESHOLD_VELOCITY = 200;

    private static final int MENU_NEW_LIST = Menu.FIRST;
    private static final int MENU_CLEAN_UP_LIST = Menu.FIRST + 1;
    private static final int MENU_DELETE_LIST = Menu.FIRST + 2;

    private static final int MENU_SHARE = Menu.FIRST + 3;
    private static final int MENU_THEME = Menu.FIRST + 4;

    private static final int MENU_ADD_LOCATION_ALERT = Menu.FIRST + 5;

    private static final int MENU_RENAME_LIST = Menu.FIRST + 6;

    private static final int MENU_MARK_ITEM = Menu.FIRST + 7;
    private static final int MENU_EDIT_ITEM = Menu.FIRST + 8; // includes rename
    private static final int MENU_DELETE_ITEM = Menu.FIRST + 9;

    private static final int MENU_INSERT_FROM_EXTRAS = Menu.FIRST + 10; // insert
    // from
    // string
    // array
    // in
    // intent
    // extras
    private static final int MENU_COPY_ITEM = Menu.FIRST + 11;

    // TODO: Implement the following menu items
    // private static final int MENU_EDIT_LIST = Menu.FIRST + 12; // includes
    // rename
    // private static final int MENU_SORT = Menu.FIRST + 13; // sort
    // alphabetically
    // or modified
    private static final int MENU_PICK_ITEMS = Menu.FIRST + 14; // pick from
    // previously
    // used items

    // TODO: Implement "select list" action
    // that can be called by other programs.
    // private static final int MENU_SELECT_LIST = Menu.FIRST + 15; // select a
    // shopping list
    private static final int MENU_PREFERENCES = Menu.FIRST + 17;
    private static final int MENU_SEND = Menu.FIRST + 18;
    private static final int MENU_REMOVE_ITEM_FROM_LIST = Menu.FIRST + 19;
    private static final int MENU_MOVE_ITEM = Menu.FIRST + 20;
    private static final int MENU_MARK_ALL_ITEMS = Menu.FIRST + 21;
    private static final int MENU_ITEM_STORES = Menu.FIRST + 22;
    private static final int MENU_UNMARK_ALL_ITEMS = Menu.FIRST + 23;
    private static final int MENU_CONNECT = Menu.FIRST + 24;

    private static final int MENU_DISTRIBUTION_START = Menu.FIRST + 100; // MUST
    // BE
    // LAST

    private static final int DIALOG_ABOUT = 1;
    // private static final int DIALOG_TEXT_ENTRY = 2;
    private static final int DIALOG_NEW_LIST = 2;
    private static final int DIALOG_RENAME_LIST = 3;
    private static final int DIALOG_EDIT_ITEM = 4;
    private static final int DIALOG_DELETE_ITEM = 5;
    private static final int DIALOG_THEME = 6;
    public static final int DIALOG_GET_FROM_MARKET = 7;

    private static final int DIALOG_DISTRIBUTION_START = 100; // MUST BE LAST

    private static final int REQUEST_CODE_CATEGORY_ALTERNATIVE = 1;
    private static final int REQUEST_PICK_LIST = 2;

    /**
     * The main activity.
     * 
     * Displays the shopping list that was used last time.
     */
    private static final int STATE_MAIN = 0;

    /**
     * VIEW action on a item/list URI.
     */
    private static final int STATE_VIEW_LIST = 1;

    /**
     * PICK action on an dir/item URI.
     */
    private static final int STATE_PICK_ITEM = 2;

    /**
     * GET_CONTENT action on an item/item URI.
     */
    private static final int STATE_GET_CONTENT_ITEM = 3;

    /**
     * Current state
     */
    private int mState;

    /*
     * Value of PreferenceActivity.updateCount last time we called fillItems().
     */
    private int lastAppliedPrefChange = -1;

    /**
     * mode: separate dialog to add items from existing list
     */
    public static final int MODE_PICK_ITEMS_DLG = 3;

    /**
     * mode: add items from existing list
     */
    public static final int MODE_ADD_ITEMS = 2;

    /**
     * mode: I am in the shop
     */
    public static final int MODE_IN_SHOP = 1;

    private boolean mEditingFilter = false;

    /**
     * URI of current list
     */
    private Uri mListUri;

    /**
     * URI of selected item
     */
    private Uri mItemUri;

    /**
     * URI of current list and item
     */
    private Uri mListItemUri;

    /**
     * Definition of the requestCode for the subactivity.
     */
    static final private int SUBACTIVITY_LIST_SHARE_SETTINGS = 0;

    /**
     * Definition for message handler:
     */
    static final private int MESSAGE_UPDATE_CURSORS = 1;

    /**
     * Update interval for automatic requires.
     * 
     * (Workaround since ContentObserver does not work.)
     */
    private int mUpdateInterval;

    private boolean mUpdating;

    /**
     * The items to add to the shopping list.
     * 
     * Received as a string array list in the intent extras.
     */
    private List<String> mExtraItems;

    /**
     * The quantities for items to add to the shopping list.
     * 
     * Received as a string array list in the intent extras.
     */
    private List<String> mExtraQuantities;

    /**
     * The prices for items to add to the shopping list.
     * 
     * Received as a string array list in the intent extras.
     */
    private List<String> mExtraPrices;

    /**
     * The barcodes for items to add to the shopping list.
     * 
     * Received as a string array list in the intent extras.
     */
    private List<String> mExtraBarcodes;

    /**
     * The list URI received together with intent extras.
     */
    private Uri mExtraListUri;

    /**
     * Private members connected to list of shopping lists
     */
    // Temp - making it generic for tablet compatibility
    private AdapterView mShoppingListsView;
    private Cursor mCursorShoppingLists;
    private static final String[] mStringListFilter = new String[] { Lists._ID, Lists.NAME, Lists.IMAGE,
            Lists.SHARE_NAME, Lists.SHARE_CONTACTS, Lists.SKIN_BACKGROUND };
    private static final int mStringListFilterID = 0;
    private static final int mStringListFilterNAME = 1;
    private static final int mStringListFilterIMAGE = 2;
    private static final int mStringListFilterSHARENAME = 3;
    private static final int mStringListFilterSHARECONTACTS = 4;
    private static final int mStringListFilterSKINBACKGROUND = 5;

    private ShoppingItemsView mItemsView;
    // private Cursor mCursorItems;

    public static final String[] mStringItems = new String[] { ContainsFull._ID, ContainsFull.ITEM_NAME,
            ContainsFull.ITEM_IMAGE, ContainsFull.ITEM_TAGS, ContainsFull.ITEM_PRICE, ContainsFull.QUANTITY,
            ContainsFull.STATUS, ContainsFull.ITEM_ID, ContainsFull.SHARE_CREATED_BY,
            ContainsFull.SHARE_MODIFIED_BY, ContainsFull.PRIORITY, ContainsFull.ITEM_HAS_NOTE,
            ContainsFull.ITEM_UNITS };
    static final int mStringItemsCONTAINSID = 0;
    public static final int mStringItemsITEMNAME = 1;
    static final int mStringItemsITEMIMAGE = 2;
    public static final int mStringItemsITEMTAGS = 3;
    public static final int mStringItemsITEMPRICE = 4;
    public static final int mStringItemsQUANTITY = 5;
    public static final int mStringItemsSTATUS = 6;
    public static final int mStringItemsITEMID = 7;
    private static final int mStringItemsSHARECREATEDBY = 8;
    private static final int mStringItemsSHAREMODIFIEDBY = 9;
    public static final int mStringItemsPRIORITY = 10;
    public static final int mStringItemsITEMHASNOTE = 11;
    public static final int mStringItemsITEMUNITS = 12;

    private LinearLayout.LayoutParams mLayoutParamsItems;
    private int mAllowedListHeight; // Height for the list allowed in this view.

    private AutoCompleteTextView mEditText;
    private Button mButton;
    private View mFilterLayout = null;
    private Button mStoresFilterButton = null;
    private Button mTagsFilterButton = null;
    private Button mShoppingListsFilterButton = null;

    protected Context mDialogContext;

    // TODO: Set up state information for onFreeze(), ...
    // State data to be stored when freezing:
    private final String ORIGINAL_ITEM = "original item";

    // private static final String BUNDLE_TEXT_ENTRY_MENU = "text entry menu";
    // private static final String BUNDLE_CURSOR_ITEMS_POSITION =
    // "cursor items position";
    private static final String BUNDLE_ITEM_URI = "item uri";
    private static final String BUNDLE_RELATION_URI = "relation_uri";
    private static final String BUNDLE_MODE = "mode";

    private String mSortOrder;

    // Skins --------------------------

    private boolean usingListSpinner() {

        // not sure if the version should still be checked...
        // what should happen on a Froyo tablet? Still, seems
        // likely that the condition below will do something safe.
        //
        // if (Build.VERSION.SDK_INT<Build.VERSION_CODES.HONEYCOMB)
        // return true;

        // The most foolproof thing we can check here seems to be the
        // existence of the resource used in tablet mode. If the list
        // fragment exists, then Android thinks we are running on
        // something tablet-like.
        return (findViewById(android.R.id.list) == null);

    }

    /**
     * Remember position for screen orientation change.
     */
    // int mEditItemPosition = -1;

    // public int mPriceVisibility;
    // private int mTagsVisibility;
    private SensorManager mSensorManager;
    private ShakeSensorListener mMySensorListener = new ShakeSensorListener() {

        @Override
        public void onShake() {
            // Provide some visual feedback.
            Animation shake = AnimationUtils.loadAnimation(ShoppingActivity.this, R.anim.shake);
            findViewById(R.id.background).startAnimation(shake);

            cleanupList();
        }

    };

    /**
     * isActive is true only after onResume() and before onPause().
     */
    private boolean mIsActive = false;

    /**
     * Whether to use the sensor for shake.
     */
    private boolean mUseSensor = false;
    private Uri mRelationUri;
    private int mMoveItemPosition;

    private EditItemDialog.FieldType mEditItemFocusField = EditItemDialog.FieldType.ITEMNAME;
    private GestureDetector mGestureDetector;
    private View.OnTouchListener mGestureListener;

    /**
     * Called when the activity is first created.
     */
    @Override
    public void onCreate(Bundle icicle) {
        ExceptionHandler.register(this);
        super.onCreate(icicle);

        if (debug)
            Log.d(TAG, "Shopping list onCreate()");

        mSortOrder = PreferenceActivity.getShoppingListSortOrderFromPrefs(this);

        mDistribution.setFirst(MENU_DISTRIBUTION_START, DIALOG_DISTRIBUTION_START);

        setContentView(R.layout.activity_shopping);

        // mEditItemPosition = -1;

        // Automatic requeries (once a second)
        mUpdateInterval = 2000;
        mUpdating = false;

        // General Uris:
        mListUri = ShoppingContract.Lists.CONTENT_URI;
        mItemUri = ShoppingContract.Items.CONTENT_URI;
        mListItemUri = ShoppingContract.Items.CONTENT_URI;

        int defaultShoppingList = getLastUsedListFromPrefs();

        // Handle the calling intent
        final Intent intent = getIntent();
        final String type = intent.resolveType(this);
        final String action = intent.getAction();

        if (action == null) {
            // Main action
            mState = STATE_MAIN;

            mListUri = Uri.withAppendedPath(ShoppingContract.Lists.CONTENT_URI, "" + defaultShoppingList);

            intent.setData(mListUri);
        } else if (Intent.ACTION_MAIN.equals(action)) {
            // Main action
            mState = STATE_MAIN;

            mListUri = Uri.withAppendedPath(ShoppingContract.Lists.CONTENT_URI, "" + defaultShoppingList);

            intent.setData(mListUri);

        } else if (Intent.ACTION_VIEW.equals(action)) {
            mState = STATE_VIEW_LIST;

            if (ShoppingContract.ITEM_TYPE.equals(type)) {
                mListUri = ShoppingUtils.getListForItem(this, intent.getData().getLastPathSegment());
            } else if (intent.getData() != null) {
                mListUri = intent.getData();
            }
        } else if (Intent.ACTION_INSERT.equals(action)) {
            // TODO: insert items from extras ????
            mState = STATE_VIEW_LIST;

            if (ShoppingContract.ITEM_TYPE.equals(type)) {
                mListUri = ShoppingUtils.getListForItem(getApplicationContext(),
                        intent.getData().getLastPathSegment());
            } else if (intent.getData() != null) {
                mListUri = intent.getData();
            }

        } else if (Intent.ACTION_PICK.equals(action)) {
            mState = STATE_PICK_ITEM;

            mListUri = Uri.withAppendedPath(ShoppingContract.Lists.CONTENT_URI, "" + defaultShoppingList);
        } else if (Intent.ACTION_GET_CONTENT.equals(action)) {
            mState = STATE_GET_CONTENT_ITEM;

            mListUri = Uri.withAppendedPath(ShoppingContract.Lists.CONTENT_URI, "" + defaultShoppingList);
        } else if (GeneralIntents.ACTION_INSERT_FROM_EXTRAS.equals(action)) {
            if (ShoppingListIntents.TYPE_STRING_ARRAYLIST_SHOPPING.equals(type)) {
                /*
                 * Need to insert new items from a string array in the intent
                 * extras Use main action but add an item to the options menu
                 * for adding extra items
                 */
                getShoppingExtras(intent);
                mState = STATE_MAIN;
                mListUri = Uri.withAppendedPath(ShoppingContract.Lists.CONTENT_URI, "" + defaultShoppingList);
                intent.setData(mListUri);
            } else if (intent.getDataString().startsWith(ShoppingContract.Lists.CONTENT_URI.toString())) {
                // Somewhat quick fix to pass data from ShoppingListsActivity to
                // this activity.

                // We received a valid shopping list URI:
                mListUri = intent.getData();

                getShoppingExtras(intent);
                mState = STATE_MAIN;
                intent.setData(mListUri);
            }
        } else {
            // Unknown action.
            Log.e(TAG, "Shopping: Unknown action, exiting");
            finish();
            return;
        }

        // hook up all buttons, lists, edit text:
        createView();

        // populate the lists
        fillListFilter();

        // Get last part of URI:
        int selectList;
        try {
            selectList = Integer.parseInt(mListUri.getLastPathSegment());
        } catch (NumberFormatException e) {
            selectList = defaultShoppingList;
        }

        // select the default shopping list at the beginning:
        setSelectedListId(selectList);

        if (icicle != null) {
            String prevText = icicle.getString(ORIGINAL_ITEM);
            if (prevText != null) {
                mEditText.setTextKeepState(prevText);
            }
            // mTextEntryMenu = icicle.getInt(BUNDLE_TEXT_ENTRY_MENU);
            // mEditItemPosition = icicle.getInt(BUNDLE_CURSOR_ITEMS_POSITION);
            mItemUri = Uri.parse(icicle.getString(BUNDLE_ITEM_URI));
            List<String> pathSegs = mItemUri.getPathSegments();
            int num = pathSegs.size();
            mListItemUri = Uri.withAppendedPath(mListUri, pathSegs.get(num - 1));
            if (icicle.containsKey(BUNDLE_RELATION_URI)) {
                mRelationUri = Uri.parse(icicle.getString(BUNDLE_RELATION_URI));
            }
            mItemsView.mMode = icicle.getInt(BUNDLE_MODE);
        }

        // set focus to the edit line:
        mEditText.requestFocus();

        // TODO remove initFromPreferences from onCreate
        // we need it in resume to update after settings have changed
        initFromPreferences();
        // now update title and fill all items
        onModeChanged();

        mItemsView.setActionBarListener(this);
    }

    private static final String[] SYNC_PROJECTION = new String[] { Contains.STATUS, Contains.ITEM_ID,
            Contains.LIST_ID, Contains.MODIFIED_DATE, };

    final Handler handler = new Handler();

    private class SynchronizationContentObserver extends ContentObserver {

        private boolean unregistering = false;

        public SynchronizationContentObserver() {
            super(handler);
        }

        @Override
        public void onChange(boolean selfChange, Uri uri) {
            super.onChange(selfChange);
            if (!isUnregistered()) {
                final String path = uri.toString();
                if (path.startsWith(Contains.CONTENT_URI.toString())
                        || path.startsWith(Items.CONTENT_URI.toString())) {
                    unregisterSyncObserver();
                    sync();
                }
            }
        };

        public void unregister() {
            unregistering = true;
            getContentResolver().unregisterContentObserver(this);
        }

        boolean isUnregistered() {
            return unregistering;
        }

    }

    private SynchronizationContentObserver usersSyncObserver = null;
    private SynchronizationContentObserver containsSyncObserver = null;

    private synchronized void registerSyncObserver() {
        if (usersSyncObserver == null || usersSyncObserver.isUnregistered()) {
            usersSyncObserver = new SynchronizationContentObserver();
            getContentResolver().registerContentObserver(Items.CONTENT_URI, true, usersSyncObserver);
        }
        if (containsSyncObserver == null || containsSyncObserver.isUnregistered()) {
            containsSyncObserver = new SynchronizationContentObserver();
            getContentResolver().registerContentObserver(Contains.CONTENT_URI, true, containsSyncObserver);
        }
    }

    private synchronized void unregisterSyncObserver() {
        usersSyncObserver.unregister();
        containsSyncObserver.unregister();
    }

    static boolean synchronizingRightNow = false;

    class SynchronizationResultProcessor extends JsonProcessor {

        final String listId;

        SynchronizationResultProcessor(String listId) {
            synchronizingRightNow = true;
            this.listId = listId;
        }

        @Override
        public void run(JSONObject result) {
            if (result != null) {
                String text = "";
                try {
                    switch (result.getInt(Connectivity.RESULT_RESPONSE)) {
                    case Connectivity.RESULT_CODE_SUCCESS:

                        JSONArray list = result.getJSONArray(Connectivity.RESULT_RESPONSE_LIST);
                        int recievedCount = list.length();
                        //unregisterSyncObserver();
                        for (int i = 0; i < recievedCount; ++i) {
                            JSONObject item = list.getJSONObject(i);

                            ContentValues values = new ContentValues();
                            String name = item.getString(ShoppingContract.Items.NAME);
                            long status = item.getLong(ShoppingContract.Contains.STATUS);
                            final String modified = item.getString(ShoppingContract.Items.MODIFIED_DATE);
                            if (status == -1) { // delete
                                long itemId = ShoppingUtils.getItemId(ShoppingActivity.this, name);
                                if (itemId != -1) {
                                    String itemIdStr = String.valueOf(itemId);
                                    Cursor c = getContentResolver().query(Contains.CONTENT_URI,
                                            new String[] { Contains.LIST_ID, Contains.ITEM_ID,
                                                    Contains.MODIFIED_DATE },
                                            "list_id=? and item_id=? and modified<?",
                                            new String[] { listId, itemIdStr, modified }, null);
                                    if (c.getCount() > 0)
                                        ShoppingUtils.deleteItem(ShoppingActivity.this, itemIdStr, listId);
                                    c.close();
                                }
                            } else { // create or update
                                values.put(ShoppingContract.Items.NAME, name);
                                values.put(ShoppingContract.Items.MODIFIED_DATE, modified);
                                values.put(ShoppingContract.Contains.STATUS, status);
                                ShoppingUtils.updateOrCreateItem(ShoppingActivity.this, listId, name, values);
                            }
                        }
                        //                  registerSyncObserver();

                        mItemsView.fillItems(ShoppingActivity.this, Long.parseLong(listId));
                        fillAutoCompleteTextViewAdapter();

                        text = "Synchronized with server.";

                        break;

                    default:
                        //Assert.assertFalse(true);
                        text = "??";
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                    Assert.assertFalse(true);
                    text = "??";
                }
                if (!ShoppingActivity.this.isFinishing()) {
                    Toast.makeText(ShoppingActivity.this, text, Toast.LENGTH_LONG).show();
                    registerSyncObserver();
                }
            }
            synchronizingRightNow = false;
        }
    };

    /// send updates to server and receive updates of other devices
    protected void sync() {
        if (!synchronizingRightNow) {
            final long selectedListId = mItemsView.getListId();
            String listId = String.valueOf(selectedListId);
            final String listShareUuid = getListShareId(listId);
            if (selectedListId != -1 && listShareUuid != null) {
                ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);

                NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
                // check connection 
                boolean isConnected = activeNetwork != null && activeNetwork.isConnectedOrConnecting();
                // boolean isWiFi = activeNetwork.getType() == ConnectivityManager.TYPE_WIFI; // should not matter. 
                // if user opens its application and his device is connected to Internet than most likely he need to download updates. 
                // Anyway generated traffic not too high.

                if (isConnected) {
                    // Get Delayed data
                    String selection = "list_id = ?";
                    // "list_id = ? and " + ShoppingContract.Items.IS_SYNCED + "='0'";

                    Cursor containsCursor = getBaseContext().getContentResolver().query(Contains.CONTENT_URI,
                            SYNC_PROJECTION, selection, new String[] { listId }, null);
                    containsCursor.moveToFirst();
                    JSONObject syncJson = new JSONObject();
                    JSONArray itemsToSend = new JSONArray();
                    while (!containsCursor.isAfterLast()) {
                        int status = containsCursor.getInt(containsCursor.getColumnIndex(Contains.STATUS));
                        long itemId = containsCursor.getLong(containsCursor.getColumnIndex(Contains.ITEM_ID));
                        long modified = containsCursor
                                .getLong(containsCursor.getColumnIndex(Contains.MODIFIED_DATE));
                        try {
                            JSONObject item = new JSONObject();
                            item.put(ShoppingContract.ContainsFull.STATUS, status);

                            Uri itemUri = Uri.withAppendedPath(Items.CONTENT_URI, String.valueOf(itemId));
                            Cursor itemsCursor = getBaseContext().getContentResolver().query(itemUri,
                                    new String[] { Items.MODIFIED_DATE, Items.NAME }, null, null, null);
                            itemsCursor.moveToFirst();
                            long itemsModifiedTimestamp = itemsCursor
                                    .getLong(itemsCursor.getColumnIndex(Items.MODIFIED_DATE));
                            modified = Math.max(modified, itemsModifiedTimestamp);
                            item.put(Items.MODIFIED_DATE, modified);
                            String name = itemsCursor.getString(itemsCursor.getColumnIndex(Items.NAME));
                            item.put(Items.NAME, name);
                            itemsCursor.close();

                            itemsToSend.put(item);

                        } catch (JSONException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }

                        containsCursor.moveToNext();
                    }
                    containsCursor.close();
                    try {
                        syncJson.put(Connectivity.UPDATE_ITEM_COMMAND, itemsToSend);
                    } catch (JSONException e1) {
                        // TODO Auto-generated catch block
                        e1.printStackTrace();
                    }

                    Connectivity.Request(this, new SynchronizationResultProcessor(listId),
                            new BasicNameValuePair(Connectivity.SYNC_COMMAND, syncJson.toString()),
                            new BasicNameValuePair(Connectivity.UUID, listShareUuid));
                } else // isConnected
                {
                    // TODO : subscribe to detect when connection back
                }
            }
        }
    }

    private String getListShareId() {
        long listId = mItemsView.getListId();
        return listId == -1 ? null : getListShareId(String.valueOf(listId));
    }

    /**
     * @param listId
     * @return list share id
     */
    private String getListShareId(String listId) {
        Uri listUri = Uri.withAppendedPath(Lists.CONTENT_URI, listId);
        Cursor listCursor = getBaseContext().getContentResolver().query(listUri, new String[] { Lists.SHARE_ID },
                null, null, null);
        listCursor.moveToFirst();
        final String listShareId = listCursor.getString(listCursor.getColumnIndex(Lists.SHARE_ID));
        listCursor.close();
        return listShareId;
    }

    @Override
    public void onStop() {
        super.onStop();
        updateWidgets();
    }

    private void updateWidgets() {
        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
        int[] a = appWidgetManager
                .getAppWidgetIds(new ComponentName(this.getPackageName(), CheckItemsWidget.class.getName()));
        List<AppWidgetProviderInfo> b = appWidgetManager.getInstalledProviders();
        for (AppWidgetProviderInfo i : b) {
            if (i.provider.getPackageName().equals(this.getPackageName())) {
                a = appWidgetManager.getAppWidgetIds(i.provider);
                new CheckItemsWidget().onUpdate(this, appWidgetManager, a);
            }
        }
    }

    // used at startup, otherwise use getSelectedListId
    private int getLastUsedListFromPrefs() {

        SharedPreferences sp = getSharedPreferences("org.openintents.shopping_preferences", MODE_PRIVATE);

        return sp.getInt(PreferenceActivity.PREFS_LASTUSED, 1);
    }

    private void initFromPreferences() {

        SharedPreferences sp = getSharedPreferences("org.openintents.shopping_preferences", MODE_PRIVATE);

        if (mItemsView != null) {
            // UGLY WORKAROUND:
            // On screen orientation changes, fillItems() is called twice.
            // That is why we have to set the list position twice.
            mItemsView.mUpdateLastListPosition = 2;

            mItemsView.mLastListPosition = sp.getInt(PreferenceActivity.PREFS_LASTLIST_POSITION, 0);
            mItemsView.mLastListTop = sp.getInt(PreferenceActivity.PREFS_LASTLIST_TOP, 0);

            if (debug)
                Log.d(TAG, "Load list position: pos: " + mItemsView.mLastListPosition + ", top: "
                        + mItemsView.mLastListTop);

            // selected list must be set after changing ordering
            String sortOrder = PreferenceActivity.getShoppingListSortOrderFromPrefs(this);
            if (!mSortOrder.equals(sortOrder)) {
                mSortOrder = sortOrder;
                fillListFilter();
                setSelectedListId(getLastUsedListFromPrefs());
            } else if (getSelectedListId() == -1) {
                setSelectedListId(getLastUsedListFromPrefs());
            }
        }

        if (sp.getBoolean(PreferenceActivity.PREFS_SCREENLOCK, PreferenceActivity.PREFS_SCREENLOCK_DEFAULT)) {
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        }

        if (mItemsView != null) {
            if (sp.getBoolean(PreferenceActivity.PREFS_SHOW_PRICE, PreferenceActivity.PREFS_SHOW_PRICE_DEFAULT)) {
                mItemsView.mPriceVisibility = View.VISIBLE;
            } else {
                mItemsView.mPriceVisibility = View.GONE;
            }

            if (sp.getBoolean(PreferenceActivity.PREFS_SHOW_TAGS, PreferenceActivity.PREFS_SHOW_TAGS_DEFAULT)) {
                mItemsView.mTagsVisibility = View.VISIBLE;
            } else {
                mItemsView.mTagsVisibility = View.GONE;
            }
            if (sp.getBoolean(PreferenceActivity.PREFS_SHOW_QUANTITY,
                    PreferenceActivity.PREFS_SHOW_QUANTITY_DEFAULT)) {
                mItemsView.mQuantityVisibility = View.VISIBLE;
            } else {
                mItemsView.mQuantityVisibility = View.GONE;
            }
            if (sp.getBoolean(PreferenceActivity.PREFS_SHOW_UNITS, PreferenceActivity.PREFS_SHOW_UNITS_DEFAULT)) {
                mItemsView.mUnitsVisibility = View.VISIBLE;
            } else {
                mItemsView.mUnitsVisibility = View.GONE;
            }
            if (sp.getBoolean(PreferenceActivity.PREFS_SHOW_PRIORITY,
                    PreferenceActivity.PREFS_SHOW_PRIORITY_DEFAULT)) {
                mItemsView.mPriorityVisibility = View.VISIBLE;
            } else {
                mItemsView.mPriorityVisibility = View.GONE;
            }
        }

        mUseSensor = sp.getBoolean(PreferenceActivity.PREFS_SHAKE, PreferenceActivity.PREFS_SHAKE_DEFAULT);

        boolean nowEditingFilter = sp.getBoolean(PreferenceActivity.PREFS_USE_FILTERS,
                PreferenceActivity.PREFS_USE_FILTERS_DEFAULT);
        if (mStoresFilterButton != null && mEditingFilter != nowEditingFilter) {
            updateFilterWidgets();
            fillItems(false);
        }

        if (PreferenceActivity.getCompletionSettingChanged(this)) {
            fillAutoCompleteTextViewAdapter();
        }
    }

    private void registerSensor() {
        if (!mUseSensor) {
            // Don't use sensors
            return;
        }

        if (mItemsView.mMode == MODE_IN_SHOP) {
            if (mSensorManager == null) {
                mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
            }
            mSensorManager.registerListener(mMySensorListener, SensorManager.SENSOR_ACCELEROMETER,
                    SensorManager.SENSOR_DELAY_UI);
        }

    }

    private void unregisterSensor() {
        if (mSensorManager != null) {
            mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
            mSensorManager.unregisterListener(mMySensorListener);
        }
    }

    @Override
    protected void onResume() {
        if (debug)
            Log.i(TAG, "Shopping list onResume() 1");
        super.onResume();
        if (debug)
            Log.i(TAG, "Shopping list onResume() 2");

        // Reload preferences, in case something changed
        initFromPreferences();

        mIsActive = true;

        this.setRequestedOrientation(PreferenceActivity.getOrientationFromPrefs(this));

        if (getSelectedListId() != -1) {
            setListTheme(loadListTheme());
            applyListTheme();
            updateTitle();
        }
        mItemsView.onResume();

        // TODO fling disabled for release 1.3.0
        // mGestureDetector = new GestureDetector(new MyGestureDetector());
        // mGestureListener = new OnTouchListener() {
        // public boolean onTouch(View view, MotionEvent e) {
        // if (mGestureDetector.onTouchEvent(e)) {
        // return true;
        // }
        // return false;
        // }
        // };
        // mListItemsView.setOnTouchListener(mGestureListener);

        mEditText.setKeyListener(PreferenceActivity.getCapitalizationKeyListenerFromPrefs(getApplicationContext()));

        if (!mUpdating) {
            mUpdating = true;
            // mHandler.sendMessageDelayed(mHandler.obtainMessage(
            // MESSAGE_UPDATE_CURSORS), mUpdateInterval);
        }

        // OnResume can be called when exiting PreferenceActivity, in
        // which case we might need to refresh the list depending on
        // which settings were changed. We could be smarter about this,
        // but for now refresh if /any/ pref has changed.
        //
        // In phone mode list id is generally not set by now, and there will
        // soon be a fillItems call triggered by updating the list selector.
        // However when the embedded list selector is not being used, now might
        // be a good time to update for pending
        // preference changes.
        if (!usingListSpinner()) {
            fillItems(true);
        } else {
            Log.d(TAG, "Skipping fillItems()");

            // at least add items from extras
            if (mExtraItems != null) {
                insertItemsFromExtras();
            }
        }

        // TODO ???
        /*
         * // Register intent receiver for refresh intents: IntentFilter
         * intentfilter = new IntentFilter(OpenIntents.REFRESH_ACTION);
         * registerReceiver(mIntentReceiver, intentfilter);
         */

        // Items received through intents are added in
        // fillItems().

        registerSensor();

        sync();

        registerSyncObserver();

        if (debug)
            Log.i(TAG, "Shopping list onResume() finished");
    }

    private void updateTitle() {
        // Modify our overall title depending on the mode we are running in.
        if (mState == STATE_MAIN || mState == STATE_VIEW_LIST) {
            if (PreferenceActivity.getPickItemsInListFromPrefs(getApplicationContext())) {
                // 2 different modes
                if (mItemsView.mMode == MODE_IN_SHOP) {
                    setTitle(getString(R.string.shopping_title, getCurrentListName()));
                    registerSensor();
                } else {
                    setTitle(getString(R.string.pick_items_title, getCurrentListName()));
                    unregisterSensor();
                }
            } else {
                // Only one mode: "Pick items using dialog"
                // App name is default. But if using filters, include list name
                // too.
                if (PreferenceActivity.getUsingFiltersFromPrefs(this))
                    setTitle(getCurrentListName() + " - " + getText(R.string.app_name));
                else
                    setTitle(getText(R.string.app_name));
            }
        } else if ((mState == STATE_PICK_ITEM) || (mState == STATE_GET_CONTENT_ITEM)) {
            setTitle(getText(R.string.pick_item));
            setTitleColor(0xFFAAAAFF);
        }

        // also update the button label
        updateButton();
    }

    private void updateButton() {
        if (mItemsView.mMode == MODE_ADD_ITEMS) {
            String newItem = mEditText.getText().toString();
            if (TextUtils.isEmpty(newItem)) {
                // If in "add items" mode and the text field is empty,
                // set the button text to "Shopping"
                mButton.setText(R.string.menu_start_shopping);
            } else {
                mButton.setText(R.string.add);
            }
        } else {
            mButton.setText(R.string.add);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see android.app.Activity#onPause()
     */
    @Override
    protected void onPause() {

        unregisterSyncObserver();

        super.onPause();
        if (debug)
            Log.i(TAG, "Shopping list onPause()");
        if (debug)
            Log.i(TAG, "Spinner: onPause: " + mIsActive);
        mIsActive = false;
        if (debug)
            Log.i(TAG, "Spinner: onPause: " + mIsActive);

        unregisterSensor();

        // Save position and pixel position of first visible item
        // of current shopping list
        int listposition = mItemsView.getFirstVisiblePosition();
        View v = mItemsView.getChildAt(0);
        int listtop = (v == null) ? 0 : v.getTop();
        if (debug)
            Log.d(TAG, "Save list position: pos: " + listposition + ", top: " + listtop);

        SharedPreferences sp = getSharedPreferences("org.openintents.shopping_preferences", MODE_PRIVATE);
        SharedPreferences.Editor editor = sp.edit();
        // need to do something about the fact that the spinner is driving this
        // even though it isn't always used. also the long vs int is fishy.
        // but for now, just don't overwrite previous setting with -1.
        int list_id = new Long(getSelectedListId()).intValue();
        if (list_id != -1)
            editor.putInt(PreferenceActivity.PREFS_LASTUSED, list_id);
        editor.putInt(PreferenceActivity.PREFS_LASTLIST_POSITION, listposition);
        editor.putInt(PreferenceActivity.PREFS_LASTLIST_TOP, listtop);
        editor.commit();
        // TODO ???
        /*
         * // Unregister refresh intent receiver
         * unregisterReceiver(mIntentReceiver);
         */

        mItemsView.onPause();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if (debug)
            Log.i(TAG, "Shopping list onSaveInstanceState()");

        // Save original text from edit box
        String s = mEditText.getText().toString();
        outState.putString(ORIGINAL_ITEM, s);

        outState.putString(BUNDLE_ITEM_URI, mItemUri.toString());
        if (mRelationUri != null) {
            outState.putString(BUNDLE_RELATION_URI, mRelationUri.toString());
        }
        outState.putInt(BUNDLE_MODE, mItemsView.mMode);

        mUpdating = false;

        // after items have been added through an "insert from extras" the
        // action name should be different to avoid duplicate inserts e.g. on
        // rotation.
        if (mExtraItems == null && GeneralIntents.ACTION_INSERT_FROM_EXTRAS.equals(getIntent().getAction())) {
            setIntent(getIntent().setAction(Intent.ACTION_VIEW));
        }
    }

    /**
     * Hook up buttons, lists, and edittext with functionality.
     */
    private void createView() {

        // Temp-create either Spinner or List based upon the Display
        createList();

        mEditText = (AutoCompleteTextView) findViewById(R.id.autocomplete_add_item);

        fillAutoCompleteTextViewAdapter();
        mEditText.setThreshold(1);
        mEditText.setOnKeyListener(new OnKeyListener() {

            private int mLastKeyAction = KeyEvent.ACTION_UP;

            public boolean onKey(View v, int keyCode, KeyEvent key) {
                // Shortcut: Instead of pressing the button,
                // one can also press the "Enter" key.
                if (debug)
                    Log.i(TAG, "Key action: " + key.getAction());
                if (debug)
                    Log.i(TAG, "Key code: " + keyCode);
                if (keyCode == KeyEvent.KEYCODE_ENTER) {

                    if (mEditText.isPopupShowing()) {
                        mEditText.performCompletion();
                    }

                    // long key press might cause call of duplicate onKey events
                    // with ACTION_DOWN
                    // this would result in inserting an item and showing the
                    // pick list

                    if (key.getAction() == KeyEvent.ACTION_DOWN && mLastKeyAction == KeyEvent.ACTION_UP) {
                        insertNewItem();
                    }

                    mLastKeyAction = key.getAction();
                    return true;
                }
                ;
                return false;
            }
        });
        mEditText.addTextChangedListener(new TextWatcher() {

            @Override
            public void afterTextChanged(Editable s) {
                if (mItemsView.mMode == MODE_ADD_ITEMS) {
                    // small optimization: Only care about updating
                    // the button label on each key pressed if we
                    // are in "add items" mode.
                    updateButton();
                }
            }

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
            }

        });

        mButton = (Button) findViewById(R.id.button_add_item);
        mButton.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                insertNewItem();
            }
        });
        mButton.setOnLongClickListener(new View.OnLongClickListener() {

            @Override
            public boolean onLongClick(View v) {
                if (PreferenceActivity.getAddForBarcode(getApplicationContext()) == false) {
                    if (debug)
                        Log.v(TAG, "barcode scanner on add button long click disabled");
                    return false;
                }

                Intent intent = new Intent();
                intent.setData(mListUri);
                intent.setClassName("org.openintents.barcodescanner",
                        "org.openintents.barcodescanner.BarcodeScanner");
                intent.setAction(Intent.ACTION_GET_CONTENT);
                intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
                try {
                    startActivityForResult(intent, REQUEST_CODE_CATEGORY_ALTERNATIVE);
                } catch (ActivityNotFoundException e) {
                    if (debug)
                        Log.v(TAG, "barcode scanner not found");
                    showDialog(DIALOG_GET_FROM_MARKET);
                    return false;
                }

                // Instead of calling the class of barcode
                // scanner directly, a more generic approach would
                // be to use a general activity picker.
                //
                // TODO: Implement onActivityResult.
                // Problem: User has to pick activity every time.
                // Choice should be storeable in Stettings.
                // Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
                // intent.setData(mListUri);
                // intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
                //
                // Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
                // pickIntent.putExtra(Intent.EXTRA_INTENT, intent);
                // pickIntent.putExtra(Intent.EXTRA_TITLE,
                // getText(R.string.title_select_item_from));
                // try {
                // startActivityForResult(pickIntent,
                // REQUEST_CODE_CATEGORY_ALTERNATIVE);
                // } catch (ActivityNotFoundException e) {
                // Log.v(TAG, "barcode scanner not found");
                // return false;
                // }
                return true;
            }
        });

        mLayoutParamsItems = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT,
                LinearLayout.LayoutParams.WRAP_CONTENT);

        mItemsView = (ShoppingItemsView) findViewById(R.id.list_items);
        mItemsView.setThemedBackground(findViewById(R.id.background));
        mItemsView.setCustomClickListener(this);

        mItemsView.setItemsCanFocus(true);
        mItemsView.setDragListener(new DragListener() {

            @Override
            public void drag(int from, int to) {
                if (debug)
                    Log.v("DRAG", "" + from + "/" + to);

            }
        });
        mItemsView.setDropListener(new DropListener() {

            @Override
            public void drop(int from, int to) {
                if (debug)
                    Log.v("DRAG", "" + from + "/" + to);

            }
        });

        TextView tv = (TextView) findViewById(R.id.total_1);
        mItemsView.setTotalCheckedTextView(tv);

        tv = (TextView) findViewById(R.id.total_2);
        mItemsView.setTotalTextView(tv);

        tv = (TextView) findViewById(R.id.total_3);
        mItemsView.setPrioritySubtotalTextView(tv);

        tv = (TextView) findViewById(R.id.count);
        mItemsView.setCountTextView(tv);

        mItemsView.setOnItemClickListener(new OnItemClickListener() {

            public void onItemClick(AdapterView parent, View v, int pos, long id) {
                Cursor c = (Cursor) parent.getItemAtPosition(pos);
                onCustomClick(c, pos, EditItemDialog.FieldType.ITEMNAME, v);
                // DO NOT CLOSE THIS CURSOR - IT IS A MANAGED ONE.
                // ---- c.close();
            }

        });

        mItemsView.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {

            public void onCreateContextMenu(ContextMenu contextmenu, View view, ContextMenuInfo info) {
                contextmenu.add(0, MENU_EDIT_ITEM, 0, R.string.menu_edit_item).setShortcut('1', 'e');
                contextmenu.add(0, MENU_MARK_ITEM, 0, R.string.menu_mark_item).setShortcut('2', 'm');
                contextmenu.add(0, MENU_ITEM_STORES, 0, R.string.menu_item_stores).setShortcut('3', 's');
                contextmenu.add(0, MENU_REMOVE_ITEM_FROM_LIST, 0, R.string.menu_remove_item).setShortcut('4', 'r');
                contextmenu.add(0, MENU_COPY_ITEM, 0, R.string.menu_copy_item).setShortcut('5', 'c');
                contextmenu.add(0, MENU_DELETE_ITEM, 0, R.string.menu_delete_item).setShortcut('6', 'd');
                contextmenu.add(0, MENU_MOVE_ITEM, 0, R.string.menu_move_item).setShortcut('7', 'l');
            }

        });
    }

    private void createList() {

        // TODO switch layout on screen size, not sdk versions
        if (!usingListSpinner()) {

            mShoppingListsView = (ListView) findViewById(android.R.id.list);
            ((ListView) mShoppingListsView).setOnItemSelectedListener(new OnItemSelectedListener() {
                public void onItemSelected(AdapterView parent, View v, int position, long id) {
                    if (debug)
                        Log.d(TAG, "ListView: onItemSelected");

                    // Update list cursor:
                    getSelectedListId();

                    // Set the theme based on the selected list:
                    setListTheme(loadListTheme());

                    // If it's the same list we had before, requery only
                    // if a preference has changed since then.
                    fillItems(id == mItemsView.getListId());

                    // Apply the theme after the list has been filled:
                    applyListTheme();

                    updateTitle();

                    ((ListView) mShoppingListsView).setItemChecked(position, true);
                }

                public void onNothingSelected(AdapterView arg0) {
                    if (debug)
                        Log.d(TAG, "Listview: onNothingSelected: " + mIsActive);
                    if (mIsActive) {
                        fillItems(false);
                    }
                }
            });

        } else {
            mShoppingListsView = (Spinner) findViewById(R.id.spinner_listfilter);
            ((Spinner) mShoppingListsView).setOnItemSelectedListener(new OnItemSelectedListener() {
                public void onItemSelected(AdapterView parent, View v, int position, long id) {
                    if (debug)
                        Log.d(TAG, "Spinner: onItemSelected");

                    // Update list cursor:
                    getSelectedListId();

                    // Set the theme based on the selected list:
                    setListTheme(loadListTheme());

                    // If it's the same list we had before, requery only
                    // if a preference has changed since then.
                    fillItems(id == mItemsView.getListId());

                    updateTitle();

                    // Apply the theme after the list has been filled:
                    applyListTheme();

                    sync();
                }

                public void onNothingSelected(AdapterView arg0) {
                    if (debug)
                        Log.d(TAG, "Spinner: onNothingSelected: " + mIsActive);
                    if (mIsActive) {
                        fillItems(false);
                    }
                }
            });

            mShoppingListsFilterButton = (Button) findViewById(R.id.listfilter);
            if (mShoppingListsFilterButton != null) {
                mShoppingListsFilterButton.setOnClickListener(new OnClickListener() {

                    @Override
                    public void onClick(View v) {
                        showListFilter(v);
                    }
                });
            }
        }

        mStoresFilterButton = (Button) findViewById(R.id.storefilter);
        mStoresFilterButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                showStoresFilter(v);
            }
        });
        mTagsFilterButton = (Button) findViewById(R.id.tagfilter);
        mTagsFilterButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                showTagsFilter(v);
            }
        });
    }

    protected void showListFilter(final View v) {
        QuickSelectMenu popup = new QuickSelectMenu(this, v);
        int i_list;

        Menu menu = popup.getMenu();
        if (menu == null) {
            return;
        }

        // get the list of lists
        mCursorShoppingLists.requery();
        int count = mCursorShoppingLists.getCount();
        mCursorShoppingLists.moveToFirst();
        for (i_list = 0; i_list < count; i_list++) {
            String name = mCursorShoppingLists.getString(mStringListFilterNAME);
            menu.add(0, i_list, Menu.NONE, name);
            mCursorShoppingLists.moveToNext();
        }

        popup.setOnItemSelectedListener(new QuickSelectMenu.OnItemSelectedListener() {
            public void onItemSelected(CharSequence name, int pos) {
                setSelectedListPos(pos);
            }
        });

        popup.show();
    }

    protected void showStoresFilter(final View v) {
        QuickSelectMenu popup = new QuickSelectMenu(this, v);

        Menu menu = popup.getMenu();
        if (menu == null) {
            return;
        }
        Cursor c = getContentResolver().query(
                Stores.QUERY_BY_LIST_URI.buildUpon().appendPath(this.mListUri.getLastPathSegment()).build(),
                new String[] { Stores._ID, Stores.NAME }, null, null, "stores.name COLLATE NOCASE ASC");
        int i_store, count = c.getCount();
        if (count == 0) {
            c.deactivate();
            c.close();
            Toast.makeText(this, R.string.no_stores_available, Toast.LENGTH_SHORT).show();
            return;
        }

        // prepend the "no filter" option
        menu.add(0, -1, Menu.NONE, R.string.unfiltered);

        // get the list of stores
        c.moveToFirst();
        for (i_store = 0; i_store < count; i_store++) {
            long id = c.getLong(0);
            String name = c.getString(1);
            menu.add(0, (int) id, Menu.NONE, name);
            c.moveToNext();
        }
        c.deactivate();
        c.close();

        popup.setOnItemSelectedListener(new QuickSelectMenu.OnItemSelectedListener() {
            public void onItemSelected(CharSequence name, int id) {
                // set the selected store filter
                // update the filter summary? not until filter region collapsed.
                ContentValues values = new ContentValues();
                values.put(Lists.STORE_FILTER, (long) id);
                getContentResolver().update(mListUri, values, null, null);
                if (id == -1)
                    ((Button) v).setText(R.string.stores);
                else
                    ((Button) v).setText(name);
                fillItems(false);
            }
        });

        popup.show();
    }

    protected void showTagsFilter(final View v) {
        QuickSelectMenu popup = new QuickSelectMenu(this, v);

        Menu menu = popup.getMenu();
        if (menu == null) {
            return;
        }
        String[] tags = getTaglist(mListUri.getLastPathSegment());
        int i_tag, count = tags.length;

        if (count == 0) {
            Toast.makeText(this, R.string.no_tags_available, Toast.LENGTH_SHORT).show();
            return;
        }

        // prepend the "no filter" option
        menu.add(0, -1, Menu.NONE, R.string.unfiltered);

        for (i_tag = 0; i_tag < count; i_tag++) {
            menu.add(tags[i_tag]);
        }

        popup.setOnItemSelectedListener(new QuickSelectMenu.OnItemSelectedListener() {
            public void onItemSelected(CharSequence name, int id) {
                // set the selected tags filter
                ContentValues values = new ContentValues();
                values.put(Lists.TAGS_FILTER, id == -1 ? "" : (String) name);
                getContentResolver().update(mListUri, values, null, null);
                if (id == -1)
                    ((Button) v).setText(R.string.tags);
                else
                    ((Button) v).setText(name);
                fillItems(false);
            }
        });

        popup.show();
    }

    protected void updateFilterWidgets() {
        mEditingFilter = PreferenceActivity.getUsingFiltersFromPrefs(this);

        mStoresFilterButton.setVisibility(mEditingFilter ? View.VISIBLE : View.GONE);
        mTagsFilterButton.setVisibility(mEditingFilter ? View.VISIBLE : View.GONE);

        boolean showListFilter = mEditingFilter;
        if (!usingListSpinner()) {
            // Tablet mode: always show ListView
            showListFilter = false;
        }
        if (mShoppingListsFilterButton != null)
            mShoppingListsFilterButton.setVisibility(showListFilter ? View.VISIBLE : View.GONE);
        // spinner goes the opposite way
        if (mShoppingListsView != null)
            mShoppingListsView.setVisibility(showListFilter ? View.GONE : View.VISIBLE);

        if (mEditingFilter) {
            String storeName = ShoppingUtils.getListFilterStoreName(this, mListUri);
            if (storeName != null)
                mStoresFilterButton.setText(storeName);
            else
                mStoresFilterButton.setText(R.string.stores);
            String tagFilter = ShoppingUtils.getListTagsFilter(this, mListUri);
            if (tagFilter != null)
                mTagsFilterButton.setText(tagFilter);
            else
                mTagsFilterButton.setText(R.string.tags);
        }
    }

    public void onCustomClick(Cursor c, int pos, EditItemDialog.FieldType field, View clicked_view) {
        if (mState == STATE_PICK_ITEM) {
            pickItem(c);
        } else {
            if (mItemsView.mShowCheckBox) {
                boolean handled = false;
                // In default theme, there is an extra check box,
                // so clicking on anywhere else means to edit the
                // item.

                if (field == EditItemDialog.FieldType.PRICE
                        && PreferenceActivity.getUsingPerStorePricesFromPrefs(this))
                // should really be a per-list preference
                {
                    editItemStores(pos);
                    handled = true;
                }

                if ((field == EditItemDialog.FieldType.PRIORITY || field == EditItemDialog.FieldType.QUANTITY)
                        && PreferenceActivity.getQuickEditModeFromPrefs(this)) {
                    handled = QuickEditFieldPopupMenu(c, pos, field, clicked_view);
                }

                if (!handled)
                    editItem(pos, field);
            } else {
                // For themes without a checkbox, clicking anywhere means
                // to toggle the item.
                mItemsView.toggleItemBought(pos);
            }
        }
    }

    private boolean QuickEditFieldPopupMenu(final Cursor c, final int pos, final FieldType field, View v) {
        QuickSelectMenu popup = new QuickSelectMenu(this, v);

        Menu menu = popup.getMenu();
        if (menu == null) {
            return false;
        }
        menu.add("1");
        menu.add("2");
        menu.add("3");
        menu.add("4");
        menu.add("5");
        if (field == FieldType.QUANTITY) {
            menu.add(R.string.otherqty);
        }
        if (field == FieldType.PRIORITY) {
            menu.add(R.string.otherpri);
        }

        popup.setOnItemSelectedListener(new QuickSelectMenu.OnItemSelectedListener() {
            public void onItemSelected(CharSequence name, int id) {
                // TODO: use a flavor of menu.add which takes id,
                // then identifying the selection becomes easier here.

                if (name.length() > 1) {
                    // Other ... use edit dialog
                    editItem(pos, field);
                } else {
                    long number = name.charAt(0) - '0';
                    ContentValues values = new ContentValues();
                    switch (field) {
                    case PRIORITY:
                        values.put(Contains.PRIORITY, number);
                        break;
                    case QUANTITY:
                        values.put(Contains.QUANTITY, number);
                        break;
                    }
                    mItemsView.mCursorItems.moveToPosition(pos);
                    String containsId = mItemsView.mCursorItems.getString(mStringItemsCONTAINSID);
                    Uri uri = Uri.withAppendedPath(ShoppingContract.Contains.CONTENT_URI, containsId);
                    getApplicationContext().getContentResolver().update(uri, values, null, null);
                    onItemChanged(); // probably overkill
                    mItemsView.updateTotal();

                }
            }
        });

        popup.show();
        return true;
    }

    /**
     * Inserts new item from edit box into currently selected shopping list.
     */
    private void insertNewItem() {
        String newItem = mEditText.getText().toString();

        // Only add if there is something to add:
        if (newItem.compareTo("") != 0) {
            long listId = getSelectedListId();
            if (listId < 0) {
                // No valid list - probably view is not active
                // and no item is selected.
                return;
            }
            final long id = mItemsView.insertNewItem(this, newItem, null, null, null, null);
            mEditText.setText("");
            fillAutoCompleteTextViewAdapter();
        } else {
            // Open list to select item from
            pickItems();
        }
    }

    /**
     * Obtain items from extras.
     */
    private void getShoppingExtras(final Intent intent) {
        mExtraItems = intent.getExtras().getStringArrayList(ShoppingListIntents.EXTRA_STRING_ARRAYLIST_SHOPPING);
        mExtraQuantities = intent.getExtras()
                .getStringArrayList(ShoppingListIntents.EXTRA_STRING_ARRAYLIST_QUANTITY);
        mExtraPrices = intent.getExtras().getStringArrayList(ShoppingListIntents.EXTRA_STRING_ARRAYLIST_PRICE);
        mExtraBarcodes = intent.getExtras().getStringArrayList(ShoppingListIntents.EXTRA_STRING_ARRAYLIST_BARCODE);

        mExtraListUri = null;
        if ((intent.getDataString() != null)
                && (intent.getDataString().startsWith(ShoppingContract.Lists.CONTENT_URI.toString()))) {
            // We received a valid shopping list URI.

            // Set current list to received list:
            mExtraListUri = intent.getData();
            if (debug)
                Log.d(TAG, "Received extras for " + mExtraListUri.toString());
        }
    }

    /**
     * Inserts new item from string array received in intent extras.
     */
    private void insertItemsFromExtras() {
        if (mExtraItems != null) {
            // Make sure we are in the correct list:
            if (mExtraListUri != null) {
                long listId = Long.parseLong(mExtraListUri.getLastPathSegment());
                if (debug)
                    Log.d(TAG, "insert items into list " + listId);
                if (listId != getSelectedListId()) {
                    if (debug)
                        Log.d(TAG, "set new list: " + listId);
                    setSelectedListId((int) listId);
                }
                mItemsView.fillItems(this, listId);
            }

            int max = mExtraItems.size();
            int maxQuantity = (mExtraQuantities != null) ? mExtraQuantities.size() : -1;
            int maxPrice = (mExtraPrices != null) ? mExtraPrices.size() : -1;
            int maxBarcode = (mExtraBarcodes != null) ? mExtraBarcodes.size() : -1;
            for (int i = 0; i < max; i++) {
                String item = mExtraItems.get(i);
                String quantity = (i < maxQuantity) ? mExtraQuantities.get(i) : null;
                String price = (i < maxPrice) ? mExtraPrices.get(i) : null;
                String barcode = (i < maxBarcode) ? mExtraBarcodes.get(i) : null;
                if (debug)
                    Log.d(TAG, "Add item: " + item + ", quantity: " + quantity + ", price: " + price + ", barcode: "
                            + barcode);
                mItemsView.insertNewItem(this, item, quantity, null, price, barcode);
            }
            // delete the string array list of extra items so it can't be
            // inserted twice
            mExtraItems = null;
            mExtraQuantities = null;
            mExtraPrices = null;
            mExtraBarcodes = null;
            mExtraListUri = null;
        } else {
            Toast.makeText(this, R.string.no_items_available, Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * Picks an item and returns to calling activity.
     */
    private void pickItem(Cursor c) {
        long itemId = c.getLong(mStringItemsITEMID);
        Uri url = ContentUris.withAppendedId(ShoppingContract.Items.CONTENT_URI, itemId);

        Intent intent = new Intent();
        intent.setData(url);
        setResult(RESULT_OK, intent);
        finish();
    }

    // Menu

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);

        /*
         * int MENU_ACTION_WITH_TEXT=0;
         * 
         * //Temp- for backward compatibility with OS 3 features
         * 
         * if(!usingListSpinner()){ try{ //setting the value equivalent to
         * desired expression
         * //MenuItem.SHOW_AS_ACTION_IF_ROOM|MenuItem.SHOW_AS_ACTION_WITH_TEXT
         * java.lang.reflect.Field
         * field=MenuItem.class.getDeclaredField("SHOW_AS_ACTION_IF_ROOM");
         * MENU_ACTION_WITH_TEXT=field.getInt(MenuItem.class);
         * field=MenuItem.class.getDeclaredField("SHOW_AS_ACTION_WITH_TEXT");
         * MENU_ACTION_WITH_TEXT|=field.getInt(MenuItem.class); }catch(Exception
         * e){ //reset value irrespective of cause MENU_ACTION_WITH_TEXT=0; }
         * 
         * }
         */

        // Add menu option for auto adding items from string array in intent
        // extra if they exist
        if (mExtraItems != null) {
            menu.add(0, MENU_INSERT_FROM_EXTRAS, 0, R.string.menu_auto_add)
                    .setIcon(android.R.drawable.ic_menu_upload);
        }

        // Temp - Temporary item holder for compatibility framework
        MenuItem item = null;
        // Standard menu
        item = menu.add(0, MENU_NEW_LIST, 0, R.string.new_list).setIcon(R.drawable.ic_menu_add_list)
                .setShortcut('0', 'n');
        // MenuCompat.setShowAsAction(item, MenuItem.SHOW_AS_ACTION_IF_ROOM);

        item = menu.add(0, MENU_CLEAN_UP_LIST, 0, R.string.clean_up_list).setIcon(R.drawable.ic_menu_cleanup)
                .setShortcut('1', 'c');
        MenuCompat.setShowAsAction(item, MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);

        menu.add(0, MENU_PICK_ITEMS, 0, R.string.menu_pick_items).setIcon(android.R.drawable.ic_menu_add)
                .setShortcut('2', 'p');

        menu.add(0, MENU_SHARE, 0, R.string.menu_share).setIcon(android.R.drawable.ic_menu_send).setShortcut('4',
                's');
        menu.add(0, MENU_CONNECT, 0, R.string.menu_connect).setIcon(android.R.drawable.ic_menu_send);

        menu.add(0, MENU_THEME, 0, R.string.theme).setIcon(android.R.drawable.ic_menu_manage).setShortcut('3', 't');

        menu.add(0, MENU_PREFERENCES, 0, R.string.preferences).setIcon(android.R.drawable.ic_menu_preferences)
                .setShortcut('4', 'p');

        menu.add(0, MENU_RENAME_LIST, 0, R.string.rename_list).setIcon(android.R.drawable.ic_menu_edit)
                .setShortcut('5', 'r');

        menu.add(0, MENU_DELETE_LIST, 0, R.string.delete_list).setIcon(android.R.drawable.ic_menu_delete);

        menu.add(0, MENU_SEND, 0, R.string.send).setIcon(android.R.drawable.ic_menu_send).setShortcut('7', 's');

        if (addLocationAlertPossible()) {
            menu.add(0, MENU_ADD_LOCATION_ALERT, 0, R.string.shopping_add_alert)
                    .setIcon(android.R.drawable.ic_menu_mylocation).setShortcut('8', 'l');
        }

        menu.add(0, MENU_MARK_ALL_ITEMS, 0, R.string.mark_all_items).setIcon(android.R.drawable.ic_menu_agenda)
                .setShortcut('9', 'm');

        menu.add(0, MENU_UNMARK_ALL_ITEMS, 0, R.string.unmark_all_items);

        // Add distribution menu items last.
        mDistribution.onCreateOptionsMenu(menu);

        // NOTE:
        // Dynamically added menu items are included in onPrepareOptionsMenu()
        // instead of here!
        // (Explanation see there.)

        return true;
    }

    /**
     * Check whether an application exists that handles the pick activity.
     * 
     * @return
     */
    private boolean addLocationAlertPossible() {

        // Test whether intent exists for picking a location:
        PackageManager pm = getPackageManager();
        Intent intent = new Intent(Intent.ACTION_PICK, Locations.CONTENT_URI);
        List<ResolveInfo> resolve_pick_location = pm.queryIntentActivities(intent,
                PackageManager.MATCH_DEFAULT_ONLY);
        /*
         * for (int i = 0; i < resolve_pick_location.size(); i++) { Log.d(TAG,
         * "Activity name: " + resolve_pick_location.get(i).activityInfo.name);
         * }
         */

        // Check whether adding alerts is possible.
        intent = new Intent(Intent.ACTION_VIEW, Alert.Generic.CONTENT_URI);
        List<ResolveInfo> resolve_view_alerts = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);

        boolean pick_location_possible = (resolve_pick_location.size() > 0);
        boolean view_alerts_possible = (resolve_view_alerts.size() > 0);
        if (debug)
            Log.d(TAG, "Pick location possible: " + pick_location_possible);
        if (debug)
            Log.d(TAG, "View alerts possible: " + view_alerts_possible);
        if (pick_location_possible && view_alerts_possible) {
            return true;
        }

        return false;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);

        // TODO: Add item-specific menu items (see NotesList.java example)
        // like edit, strike-through, delete.

        // Add menu option for auto adding items from string array in intent
        // extra if they exist
        if (mExtraItems == null) {
            menu.removeItem(MENU_INSERT_FROM_EXTRAS);
        }

        // Selected list:
        long listId = getSelectedListId();

        // set menu title for change mode
        MenuItem menuItem = menu.findItem(MENU_PICK_ITEMS);

        if (mItemsView.mMode == MODE_ADD_ITEMS) {
            menuItem.setTitle(R.string.menu_start_shopping);
            menuItem.setIcon(android.R.drawable.ic_menu_myplaces);
        } else {
            menu.findItem(MENU_PICK_ITEMS).setTitle(R.string.menu_pick_items);
            menuItem.setIcon(android.R.drawable.ic_menu_add);
        }

        menuItem = menu.findItem(MENU_MARK_ALL_ITEMS)
                .setVisible(mItemsView.getMarkedAllStatus() == null || !mItemsView.getMarkedAllStatus());
        menuItem = menu.findItem(MENU_UNMARK_ALL_ITEMS)
                .setVisible(mItemsView.getMarkedAllStatus() != null && mItemsView.getMarkedAllStatus());

        menuItem = menu.findItem(MENU_CLEAN_UP_LIST).setEnabled(mItemsView.mNumChecked > 0);

        // Delete list is possible, if we have more than one list:
        // AND
        // the current list is not the default list (listId == 0) - issue #105
        // TODO: Later, the default list should be user-selectable,
        // and not deletable.

        // TODO ???
        /*
         * menu.setItemShown(MENU_DELETE_LIST, mCursorListFilter.count() > 1 &&
         * listId != 1); // 1 is hardcoded number of default first list.
         */

        // The following code is put from onCreateOptionsMenu to
        // onPrepareOptionsMenu,
        // because the URI of the shopping list can change if the user switches
        // to another list.
        // Generate any additional actions that can be performed on the
        // overall list. This allows other applications to extend
        // our menu with their own actions.
        Intent intent = new Intent(null, getIntent().getData());
        intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
        // menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0,
        // new ComponentName(this, NoteEditor.class), null, intent, 0, null);

        // Workaround to add icons:
        MenuIntentOptionsWithIcons menu2 = new MenuIntentOptionsWithIcons(this, menu);
        menu2.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0,
                new ComponentName(this, org.openintents.shopping.ShoppingActivity.class), null, intent, 0, null);

        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (debug)
            Log.d(TAG, "onOptionsItemSelected getItemId: " + item.getItemId());
        Intent intent;
        switch (item.getItemId()) {
        case MENU_NEW_LIST:
            showDialog(DIALOG_NEW_LIST);
            return true;

        case MENU_CLEAN_UP_LIST:
            cleanupList();
            return true;

        case MENU_RENAME_LIST:
            showDialog(DIALOG_RENAME_LIST);
            return true;

        case MENU_DELETE_LIST:
            deleteListConfirm();
            return true;

        case MENU_PICK_ITEMS:
            pickItems();
            return true;

        case MENU_SHARE:
            shareListId();
            return true;

        case MENU_CONNECT:
            IntentIntegrator intentIntegrator = new IntentIntegrator(this);
            intentIntegrator.initiateScan(IntentIntegrator.QR_CODE_TYPES);
            // see onActivityResult for result processing
            return true;

        case MENU_THEME:
            setThemeSettings();
            return true;

        case MENU_ADD_LOCATION_ALERT:
            addLocationAlert();
            return true;

        case MENU_PREFERENCES:
            intent = new Intent(this, PreferenceActivity.class);
            startActivity(intent);
            return true;
        case MENU_SEND:
            sendList();
            return true;
        case MENU_INSERT_FROM_EXTRAS:
            insertItemsFromExtras();
            return true;
        case MENU_MARK_ALL_ITEMS:
            mItemsView.toggleAllItems(true);
            return true;
        case MENU_UNMARK_ALL_ITEMS:
            mItemsView.toggleAllItems(false);
        }
        if (debug)
            Log.d(TAG, "Start intent group id : " + item.getGroupId());
        if (Menu.CATEGORY_ALTERNATIVE == item.getGroupId()) {
            // Start alternative cateogory intents with option to return a
            // result.
            if (debug)
                Log.d(TAG, "Start alternative intent for : " + item.getIntent().getDataString());
            startActivityForResult(item.getIntent(), REQUEST_CODE_CATEGORY_ALTERNATIVE);
            return true;
        }
        return super.onOptionsItemSelected(item);

    }

    /**
     * 
     */
    private void pickItems() {
        if (PreferenceActivity.getPickItemsInListFromPrefs(getApplicationContext())) {
            if (mItemsView.mMode == MODE_IN_SHOP) {
                mItemsView.mMode = MODE_ADD_ITEMS;
            } else {
                mItemsView.mMode = MODE_IN_SHOP;
            }
            onModeChanged();
        } else {
            if (mItemsView.mMode != MODE_IN_SHOP) {
                mItemsView.mMode = MODE_IN_SHOP;
                onModeChanged();
            }
            pickItemsUsingDialog();
        }
    }

    private void pickItemsUsingDialog() {
        Intent intent;
        intent = new Intent(this, PickItemsActivity.class);
        intent.setData(mListUri);
        startActivity(intent);
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        super.onContextItemSelected(item);
        AdapterContextMenuInfo menuInfo = (AdapterContextMenuInfo) item.getMenuInfo();
        switch (item.getItemId()) {
        case MENU_MARK_ITEM:
            markItem(menuInfo.position);
            break;
        case MENU_EDIT_ITEM:
            editItem(menuInfo.position, EditItemDialog.FieldType.ITEMNAME);
            break;
        case MENU_REMOVE_ITEM_FROM_LIST:
            removeItemFromList(menuInfo.position);
            break;
        case MENU_DELETE_ITEM:
            deleteItemDialog(menuInfo.position);
            break;
        case MENU_MOVE_ITEM:
            Intent intent = new Intent();
            intent.setAction(Intent.ACTION_PICK);
            intent.setData(ShoppingContract.Lists.CONTENT_URI);
            startActivityForResult(intent, REQUEST_PICK_LIST);
            mMoveItemPosition = menuInfo.position;
            break;
        case MENU_COPY_ITEM:
            copyItem(menuInfo.position);
            break;
        case MENU_ITEM_STORES:
            editItemStores(menuInfo.position);
            break;
        }

        return true;
    }

    // /////////////////////////////////////////////////////
    //
    // Menu functions
    //

    /**
     * Creates a new list from dialog.
     * 
     * @return true if new list was created. False if new list was not created,
     *         because user has not given any name.
     */
    private boolean createNewList(String name) {

        if (name.equals("")) {
            // User has not provided any name
            Toast.makeText(this, getString(R.string.please_enter_name), Toast.LENGTH_SHORT).show();
            return false;
        }

        String previousTheme = loadListTheme();

        int newId = (int) ShoppingUtils.getList(this, name);
        fillListFilter();

        setSelectedListId(newId);

        // Now set the theme based on the selected list:
        saveListTheme(previousTheme);
        setListTheme(previousTheme);
        applyListTheme();

        return true;
    }

    private void setListTheme(String theme) {
        mItemsView.setListTheme(theme);
    }

    private void applyListTheme() {
        mItemsView.applyListTheme();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            // In Holo themes, apply the theme text color also to the
            // input box and the button, because the background is
            // semi-transparent.
            mEditText.setTextColor(mItemsView.mTextColor);
            mButton.setTextColor(mItemsView.mTextColor);
            if (mStoresFilterButton != null)
                mStoresFilterButton.setTextColor(mItemsView.mTextColor);
            if (mTagsFilterButton != null)
                mTagsFilterButton.setTextColor(mItemsView.mTextColor);
            if (mShoppingListsFilterButton != null)
                mShoppingListsFilterButton.setTextColor(mItemsView.mTextColor);
            if (mShoppingListsView instanceof Spinner) {
                View view = ((Spinner) mShoppingListsView).getChildAt(0);
                setSpinnerTextColorInHoloTheme(view);
            }
        }
    }

    /**
     * For holo themes with transparent widgets, set font color of the spinner
     * using theme color.
     * 
     * @param view
     */
    private void setSpinnerTextColorInHoloTheme(View view) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            if (view instanceof TextView) {
                ((TextView) view).setTextColor(mItemsView.mTextColor);
            }
        }
    }

    /**
     * Rename list from dialog.
     * 
     * @return true if new list was renamed. False if new list was not renamed,
     *         because user has not given any name.
     */
    private boolean renameList(String newName) {

        if (newName.equals("")) {
            // User has not provided any name
            Toast.makeText(this, getString(R.string.please_enter_name), Toast.LENGTH_SHORT).show();
            return false;
        }

        // Rename currently selected list:
        ContentValues values = new ContentValues();
        values.put(Lists.NAME, "" + newName);
        getContentResolver().update(Uri.withAppendedPath(Lists.CONTENT_URI, mCursorShoppingLists.getString(0)),
                values, null, null);

        mCursorShoppingLists.requery();
        updateTitle();
        return true;
    }

    private void sendList() {
        if (mItemsView.getAdapter() instanceof CursorAdapter) {
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < mItemsView.getAdapter().getCount(); i++) {
                Cursor item = (Cursor) mItemsView.getAdapter().getItem(i);
                if (item.getLong(mStringItemsSTATUS) == ShoppingContract.Status.BOUGHT) {
                    sb.append("[X] ");
                } else {
                    sb.append("[ ] ");
                }
                String quantity = item.getString(mStringItemsQUANTITY);
                long pricecent = item.getLong(mStringItemsITEMPRICE);
                String price = PriceConverter.getStringFromCentPrice(pricecent);
                String tags = item.getString(mStringItemsITEMTAGS);
                if (!TextUtils.isEmpty(quantity)) {
                    sb.append(quantity);
                    sb.append(" ");
                }
                String units = item.getString(mStringItemsITEMUNITS);
                if (!TextUtils.isEmpty(units)) {
                    sb.append(units);
                    sb.append(" ");
                }
                sb.append(item.getString(mStringItemsITEMNAME));
                // Put additional info (price, tags) in brackets
                boolean p = !TextUtils.isEmpty(price);
                boolean t = !TextUtils.isEmpty(tags);
                if (p || t) {
                    sb.append(" (");
                    if (p) {
                        sb.append(price);
                    }
                    if (p && t) {
                        sb.append(", ");
                    }
                    if (t) {
                        sb.append(tags);
                    }
                    sb.append(")");
                }
                sb.append("\n");
            }

            Intent i = new Intent();
            i.setAction(Intent.ACTION_SEND);
            i.setType("text/plain");
            i.putExtra(Intent.EXTRA_SUBJECT, getCurrentListName());
            i.putExtra(Intent.EXTRA_TEXT, sb.toString());

            try {
                startActivity(Intent.createChooser(i, getString(R.string.send)));
            } catch (ActivityNotFoundException e) {
                Toast.makeText(this, R.string.email_not_available, Toast.LENGTH_SHORT).show();
                Log.e(TAG, "Email client not installed");
            }
        } else {
            Toast.makeText(this, R.string.empty_list_not_sent, Toast.LENGTH_SHORT);
        }

    }

    /**
     * Clean up the currently visible shopping list by removing items from list
     * that are marked BOUGHT.
     */
    private void cleanupList() {
        // Remove all items from current list
        // which have STATUS = Status.BOUGHT

        if (!mItemsView.cleanupList()) {
            // Show toast
            Toast.makeText(this, R.string.no_items_marked, Toast.LENGTH_SHORT).show();
        }
    }

    // TODO: Convert into proper dialog that remains across screen orientation
    // changes.
    /**
     * Confirm 'delete list' command by AlertDialog.
     */
    private void deleteListConfirm() {
        new AlertDialog.Builder(this)
                // .setIcon(R.drawable.alert_dialog_icon)
                .setTitle(R.string.confirm_delete_list)
                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {
                        // click Ok
                        deleteList();
                    }
                }).setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {
                        // click Cancel
                    }
                })
                // .create()
                .show();
    }

    /**
     * Deletes currently selected shopping list.
     */
    private void deleteList() {
        String listId = mCursorShoppingLists.getString(0);
        ShoppingUtils.deleteList(this, listId);

        // Update view
        fillListFilter();

        getSelectedListId();

        // Set the theme based on the selected list:
        setListTheme(loadListTheme());

        fillItems(false);
        applyListTheme();
        updateTitle();
    }

    /** Mark item */
    void markItem(int position) {
        mItemsView.toggleItemBought(position);
    }

    /**
     * Edit item
     * 
     * @param field
     */
    void editItem(long itemId, long containsId, EditItemDialog.FieldType field) {
        mItemUri = Uri.withAppendedPath(ShoppingContract.Items.CONTENT_URI, "" + itemId);
        mListItemUri = Uri.withAppendedPath(mListUri, "" + itemId);
        mRelationUri = Uri.withAppendedPath(ShoppingContract.Contains.CONTENT_URI, "" + containsId);
        mEditItemFocusField = field;

        showDialog(DIALOG_EDIT_ITEM);
    }

    /**
     * Edit item
     * 
     * @param field
     */
    void editItem(int position, EditItemDialog.FieldType field) {
        if (debug)
            Log.d(TAG, "EditItems: Position: " + position);
        mItemsView.mCursorItems.moveToPosition(position);
        // mEditItemPosition = position;

        long itemId = mItemsView.mCursorItems.getLong(mStringItemsITEMID);
        long containsId = mItemsView.mCursorItems.getLong(mStringItemsCONTAINSID);

        editItem(itemId, containsId, field);
    }

    void editItemStores(int position) {
        if (debug)
            Log.d(TAG, "EditItemStores: Position: " + position);

        mItemsView.mCursorItems.moveToPosition(position);
        // mEditItemPosition = position;
        long itemId = mItemsView.mCursorItems.getLong(mStringItemsITEMID);

        Intent intent;
        intent = new Intent(this, ItemStoresActivity.class);
        intent.setData(mListUri.buildUpon().appendPath(String.valueOf(itemId)).build());
        startActivity(intent);
    }

    int mDeleteItemPosition;

    /** delete item */
    void deleteItemDialog(int position) {
        if (debug)
            Log.d(TAG, "EditItems: Position: " + position);
        mItemsView.mCursorItems.moveToPosition(position);
        mDeleteItemPosition = position;

        showDialog(DIALOG_DELETE_ITEM);
    }

    /** delete item */
    void deleteItem(int position) {
        Cursor c = mItemsView.mCursorItems;
        c.moveToPosition(position);

        String listId = mListUri.getLastPathSegment();
        String itemId = c.getString(mStringItemsITEMID);

        // delete on server
        JSONObject item = new JSONObject();
        try {
            item.put(Items.NAME, c.getString(mStringItemsITEMNAME));
            item.put(Contains.STATUS, -1);
            item.put(Items.MODIFIED_DATE, System.currentTimeMillis());
        } catch (JSONException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        Connectivity.Request(this, new SynchronizationResultProcessor(String.valueOf(getSelectedListId())),
                new BasicNameValuePair(Connectivity.UPDATE_ITEM_COMMAND, item.toString()),
                new BasicNameValuePair(Connectivity.UUID, getListShareId(listId)));

        ShoppingUtils.deleteItem(this, itemId, listId);

        // c.requery();
        mItemsView.requery();
        fillAutoCompleteTextViewAdapter();
    }

    /** move item */
    void moveItem(int position, int targetListId) {
        Cursor c = mItemsView.mCursorItems;
        mItemsView.mCursorItems.requery();
        c.moveToPosition(position);

        long listId = getSelectedListId();
        if (false && listId < 0) {
            // No valid list - probably view is not active
            // and no item is selected.
            return;
        }
        listId = Integer.parseInt(mListUri.getLastPathSegment());

        // Attach item to new list, preserving all other fields
        String containsId = c.getString(mStringItemsCONTAINSID);
        ContentValues cv = new ContentValues(1);
        cv.put(Contains.LIST_ID, targetListId);
        getContentResolver().update(Uri.withAppendedPath(Contains.CONTENT_URI, containsId), cv, null, null);

        mItemsView.requery();
    }

    /** copy item */
    void copyItem(int position) {
        Cursor c = mItemsView.mCursorItems;
        mItemsView.mCursorItems.requery();
        c.moveToPosition(position);
        String containsId = c.getString(mStringItemsCONTAINSID);
        Long newContainsId;
        Long newItemId;

        c = getContentResolver().query(
                Uri.withAppendedPath(Uri.withAppendedPath(Contains.CONTENT_URI, "copyof"), containsId),
                new String[] { "item_id", "contains_id" }, null, null, null);

        if (c.getCount() != 1)
            return;

        c.moveToFirst();
        newItemId = c.getLong(0);
        newContainsId = c.getLong(1);
        c.deactivate();
        c.close();

        editItem(newItemId, newContainsId, FieldType.ITEMNAME);

        // mItemsView.requery();
    }

    /** removeItemFromList */
    void removeItemFromList(int position) {
        Cursor c = mItemsView.mCursorItems;
        c.moveToPosition(position);
        // Remember old values before delete (for share below)
        String itemName = c.getString(mStringItemsITEMNAME);
        long oldstatus = c.getLong(mStringItemsSTATUS);

        // Delete item by changing its state
        ContentValues values = new ContentValues();
        values.put(Contains.STATUS, Status.REMOVED_FROM_LIST);
        values.put(Contains.MODIFIED_DATE, System.currentTimeMillis());
        if (PreferenceActivity.getResetQuantity(getApplicationContext()))
            values.put(Contains.QUANTITY, "");
        getContentResolver().update(Contains.CONTENT_URI, values, "_id = ?",
                new String[] { c.getString(mStringItemsCONTAINSID) });

        // c.requery();

        mItemsView.requery();

        // If we share items, mark item on other lists:
        // TODO ???
        /*
         * String recipients =
         * mCursorListFilter.getString(mStringListFilterSHARECONTACTS); if (!
         * recipients.equals("")) { String shareName =
         * mCursorListFilter.getString(mStringListFilterSHARENAME); long
         * newstatus = Shopping.Status.BOUGHT;
         * 
         * Log.i(TAG, "Update shared item. " + " recipients: " + recipients +
         * ", shareName: " + shareName + ", status: " + newstatus);
         * mGTalkSender.sendItemUpdate(recipients, shareName, itemName,
         * itemName, oldstatus, newstatus); }
         */
    }

    /**
     * Calls the share settings with the currently selected list.
     */
    void shareListId() {
        // Obtain URI of current list
        String shareListId = getListShareId();
        if (shareListId == null) {
            shareListId = UUID.randomUUID().toString().replaceAll("-", "");
            ContentValues values = new ContentValues();
            values.put(Lists.SHARE_ID, shareListId);
            int count = getContentResolver().update(
                    Uri.withAppendedPath(Lists.CONTENT_URI, String.valueOf(getSelectedListId())), values, null,
                    null);
            Assert.assertEquals(1, count);
            sync();
        }

        IntentIntegrator intentIntegrator = new IntentIntegrator(this);
        intentIntegrator.shareText(shareListId);

        // Call share settings as subactivity
        //      Intent intent = new Intent(OpenIntents.SET_SHARE_SETTINGS_ACTION,
        //            mListUri);
        //      startActivityForResult(intent, SUBACTIVITY_LIST_SHARE_SETTINGS);

    }

    void setThemeSettings() {
        showDialog(DIALOG_THEME);
    }

    @Override
    public String onLoadTheme() {
        return loadListTheme();
    }

    @Override
    public void onSaveTheme(String theme) {
        saveListTheme(theme);
    }

    @Override
    public void onSetTheme(String theme) {
        setListTheme(theme);
        applyListTheme();
    }

    @Override
    public void onSetThemeForAll(String theme) {
        setThemeForAll(this, theme);
    }

    /**
     * Set theme for all lists.
     * 
     * @param context
     * @param theme
     */
    public static void setThemeForAll(Context context, String theme) {
        ContentValues values = new ContentValues();
        values.put(Lists.SKIN_BACKGROUND, theme);
        context.getContentResolver().update(Lists.CONTENT_URI, values, null, null);
    }

    /**
     * Loads the theme settings for the currently selected theme.
     * 
     * Up to version 1.2.1, only one of 3 hardcoded themes are available. These
     * are stored in 'skin_background' as '1', '2', or '3'.
     * 
     * Starting in 1.2.2, also themes of other packages are allowed.
     * 
     * @return
     */
    public String loadListTheme() {
        /*
         * long listId = getSelectedListId(); if (listId < 0) { // No valid list
         * - probably view is not active // and no item is selected. return 1;
         * // return default theme }
         */

        // Return default theme if something unexpected happens:
        if (mCursorShoppingLists == null)
            return "1";
        if (mCursorShoppingLists.getPosition() < 0)
            return "1";

        // mCursorListFilter has been set to correct position
        // by calling getSelectedListId(),
        // so we can read out further elements:
        String skinBackground = mCursorShoppingLists.getString(mStringListFilterSKINBACKGROUND);

        return skinBackground;
    }

    public void saveListTheme(String theme) {
        long listId = getSelectedListId();
        if (listId < 0) {
            // No valid list - probably view is not active
            // and no item is selected.
            return; // return default theme
        }

        ContentValues values = new ContentValues();
        values.put(Lists.SKIN_BACKGROUND, theme);
        getContentResolver().update(Uri.withAppendedPath(Lists.CONTENT_URI, mCursorShoppingLists.getString(0)),
                values, null, null);

        mCursorShoppingLists.requery();
    }

    /**
     * Calls a dialog for setting the locations alert.
     */
    void addLocationAlert() {

        // Call dialog as activity
        Intent intent = new Intent(OpenIntents.ADD_LOCATION_ALERT_ACTION, mListUri);
        // startSubActivity(intent, SUBACTIVITY_ADD_LOCATION_ALERT);
        startActivity(intent);
    }

    @Override
    protected Dialog onCreateDialog(int id) {

        switch (id) {

        case DIALOG_NEW_LIST:
            return new NewListDialog(this, new DialogActionListener() {

                public void onAction(String name) {
                    createNewList(name);
                }
            });

        case DIALOG_RENAME_LIST:
            return new RenameListDialog(this, getCurrentListName(), new DialogActionListener() {

                public void onAction(String name) {
                    renameList(name);
                }
            });

        case DIALOG_EDIT_ITEM:
            return new EditItemDialog(this, mItemUri, mRelationUri, mListItemUri);

        case DIALOG_DELETE_ITEM:
            return new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert)
                    .setTitle(R.string.menu_delete_item).setMessage(R.string.delete_item_confirm)
                    .setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int whichButton) {
                            deleteItem(mDeleteItemPosition);
                        }
                    }).setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int whichButton) {
                            // Don't do anything
                        }
                    }).create();

        case DIALOG_THEME:
            return new ThemeDialog(this, this);

        case DIALOG_GET_FROM_MARKET:
            return new DownloadOIAppDialog(this, DownloadOIAppDialog.OI_BARCODESCANNER);
        }
        return super.onCreateDialog(id);

    }

    @Override
    protected void onPrepareDialog(int id, Dialog dialog) {
        super.onPrepareDialog(id, dialog);

        switch (id) {

        case DIALOG_RENAME_LIST:
            ((RenameListDialog) dialog).setName(getCurrentListName());
            break;

        case DIALOG_EDIT_ITEM:
            EditItemDialog d = (EditItemDialog) dialog;
            d.setItemUri(mItemUri, mListItemUri);
            d.setRelationUri(mRelationUri);
            d.setFocusField(mEditItemFocusField);

            String[] taglist = getTaglist();
            d.setTagList(taglist);

            d.setOnItemChangedListener(this);
            break;

        case DIALOG_THEME:
            ((ThemeDialog) dialog).prepareDialog();
            break;
        case DIALOG_GET_FROM_MARKET:
            DownloadOIAppDialog.onPrepareDialog(this, dialog);
            break;
        }
    }

    // /////////////////////////////////////////////////////
    //
    // Helper functions
    //
    /**
     * Returns the ID of the selected shopping list.
     * 
     * As a side effect, the item URI is updated. Returns -1 if nothing is
     * selected.
     * 
     * @return ID of selected shopping list.
     */
    private long getSelectedListId() {
        if (mShoppingListsView == null)
            return -1;

        int pos = mShoppingListsView.getSelectedItemPosition();
        // Temp- Due to architecture requirements of OS 3, the value can not be
        // passed directly
        if (pos == -1 && !usingListSpinner()) {
            try {
                pos = (Integer) mShoppingListsView.getTag();
                pos = mCursorShoppingLists.getCount() <= pos ? -1 : pos;
            } catch (Exception e) {
                // e.printStackTrace();
            }
        }
        if (pos < 0) {
            // nothing selected - probably view is out of focus:
            // Do nothing.
            return -1;
        }

        // Obtain Id of currently selected shopping list:
        mCursorShoppingLists.moveToPosition(pos);

        long listId = mCursorShoppingLists.getLong(mStringListFilterID);

        mListUri = Uri.withAppendedPath(ShoppingContract.Lists.CONTENT_URI, "" + listId);

        getIntent().setData(mListUri);

        return listId;
    };

    /**
     * sets the selected list to a specific list Id
     */
    private void setSelectedListId(int id) {
        // Is there a nicer way to accomplish the following?
        // (we look through all elements to look for the
        // one entry that has the same ID as returned by
        // getDefaultList()).
        //
        // unfortunately, a SQL query won't work, as it would
        // return 1 row, but I still would not know which
        // row in the mCursorListFilter corresponds to that id.
        //
        // one could use: findViewById() but how will this
        // translate to the position in the list?
        mCursorShoppingLists.moveToPosition(-1);
        while (mCursorShoppingLists.moveToNext()) {
            int posId = mCursorShoppingLists.getInt(mStringListFilterID);
            if (posId == id) {
                int row = mCursorShoppingLists.getPosition();

                // if we found the Id, then select this in
                // the Spinner:
                setSelectedListPos(row);
                break;
            }
        }
    }

    private void setSelectedListPos(int pos) {
        mShoppingListsView.setTag(pos);

        mShoppingListsView.setSelection(pos);

        long id = getSelectedListId();

        // Set the theme based on the selected list:
        setListTheme(loadListTheme());

        if (id != mItemsView.getListId()) {
            fillItems(false);
        }
        applyListTheme();
        updateTitle();
        if (mShoppingListsView instanceof ListView) {
            ((ListView) mShoppingListsView).setItemChecked(pos, true);
        }
    }

    /**
      *
      */
    private void fillListFilter() {
        // Get a cursor with all lists

        mCursorShoppingLists = getContentResolver().query(Lists.CONTENT_URI, mStringListFilter, null, null,
                mSortOrder);
        startManagingCursor(mCursorShoppingLists);

        if (mCursorShoppingLists == null) {
            Log.e(TAG, "missing shopping provider");
            ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_spinner_item,
                    new String[] { getString(R.string.no_shopping_provider) });
            setSpinnerListAdapter(adapter);

            return;
        }

        if (mCursorShoppingLists.getCount() < 1) {
            // We have to create default shopping list:
            long listId = ShoppingUtils.getList(this, getText(R.string.my_shopping_list).toString());

            // Check if insertion really worked. Otherwise
            // we may end up in infinite recursion.
            if (listId < 0) {
                // for some reason insertion did not work.
                return;
            }

            // The insertion should have worked, so let us call ourselves
            // to try filling the list again:
            fillListFilter();
            return;
        }

        class mListContentObserver extends ContentObserver {

            public mListContentObserver(Handler handler) {
                super(handler);
                if (debug)
                    Log.i(TAG, "mListContentObserver: Constructor");
            }

            /*
             * (non-Javadoc)
             * 
             * @see android.database.ContentObserver#deliverSelfNotifications()
             */
            @Override
            public boolean deliverSelfNotifications() {
                // TODO Auto-generated method stub
                if (debug)
                    Log.i(TAG, "mListContentObserver: deliverSelfNotifications");
                return super.deliverSelfNotifications();
            }

            /*
             * (non-Javadoc)
             * 
             * @see android.database.ContentObserver#onChange(boolean)
             */
            @Override
            public void onChange(boolean arg0) {
                // TODO Auto-generated method stub
                if (debug)
                    Log.i(TAG, "mListContentObserver: onChange");

                mCursorShoppingLists.requery();

                super.onChange(arg0);
            }

        }
        ;
        mListContentObserver observer = new mListContentObserver(new Handler());
        mCursorShoppingLists.registerContentObserver(observer);

        // Register a ContentObserver, so that a new list can be
        // automatically detected.
        // mCursor

        /*
         * ArrayList<String> list = new ArrayList<String>(); // TODO Create
         * summary of all lists // list.add(ALL); while
         * (mCursorListFilter.next()) {
         * list.add(mCursorListFilter.getString(mStringListFilterNAME)); }
         * ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
         * android.R.layout.simple_spinner_item, list);
         * adapter.setDropDownViewResource(
         * android.R.layout.simple_spinner_dropdown_item);
         * mSpinnerListFilter.setAdapter(adapter);
         */

        SimpleCursorAdapter adapter;

        if (mShoppingListsView instanceof Spinner) {
            adapter = new HoloThemeSimpleCursorAdapter(this,
                    // Use a template that displays a text view
                    android.R.layout.simple_spinner_item,
                    // Give the cursor to the list adapter
                    mCursorShoppingLists, new String[] { Lists.NAME }, new int[] { android.R.id.text1 });
            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        } else {
            // mShoppingListView is a ListView
            adapter = new SimpleCursorAdapter(this,
                    // Use a template that displays a text view
                    R.layout.list_item_shopping_list,
                    // Give the cursor to the list adapter
                    mCursorShoppingLists, new String[] { Lists.NAME }, new int[] { R.id.text1 });
        }
        // mSpinnerListFilter.setAdapter(adapter);//Temp- redirected through
        // method
        setSpinnerListAdapter(adapter);

    }

    class HoloThemeSimpleCursorAdapter extends SimpleCursorAdapter {

        public HoloThemeSimpleCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
            super(context, layout, c, from, to);
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View view = super.getView(position, convertView, parent);
            setSpinnerTextColorInHoloTheme(view);
            return view;
        }

    }

    private void onModeChanged() {

        if (debug)
            Log.d(TAG, "onModeChanged()");
        fillItems(false);

        compat_invalidateOptionsMenu();

        updateTitle();
    }

    java.lang.reflect.Method mMethodInvalidateOptionsMenu = null;

    /**
     * Update the ActionBar (Honeycomb or higher)
     */
    private void compat_invalidateOptionsMenu() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            // invalidateOptionsMenu();
            try {
                if (mMethodInvalidateOptionsMenu == null) {
                    mMethodInvalidateOptionsMenu = Activity.class.getMethod("invalidateOptionsMenu");
                }
                mMethodInvalidateOptionsMenu.invoke(this);
            } catch (Exception e) {
                mMethodInvalidateOptionsMenu = null;
            }
        }
    }

    @Override
    public void updateActionBar() {
        compat_invalidateOptionsMenu();
    }

    private String getCurrentListName() {
        long listId = getSelectedListId();

        // calling getSelectedListId also updates mCursorShoppingLists:
        if (listId >= 0) {
            return mCursorShoppingLists.getString(mStringListFilterNAME);
        } else {
            return "";
        }
    }

    private void fillItems(boolean onlyIfPrefsChanged) {
        if (debug)
            Log.d(TAG, "fillItems()");

        long listId = getSelectedListId();
        if (listId < 0) {
            // No valid list - probably view is not active
            // and no item is selected.
            Log.d(TAG, "fillItems: listId not availalbe");
            return;
        }

        // Insert any pending items received either through intents
        // or in onActivityResult:
        if (mExtraItems != null) {
            insertItemsFromExtras();
        }

        updateFilterWidgets();

        if (onlyIfPrefsChanged && (lastAppliedPrefChange == PreferenceActivity.updateCount)) {
            return;
        }

        if (debug)
            Log.d(TAG, "fillItems() for list " + listId);
        lastAppliedPrefChange = PreferenceActivity.updateCount;
        mItemsView.fillItems(this, listId);

        // Also refresh AutoCompleteTextView:
        fillAutoCompleteTextViewAdapter();
    }

    /**
     * Fill input field (AutoCompleteTextView) with suggestions: Unique item
     * names from all lists are collected. The adapter is filled in the
     * background.
     */
    private void fillAutoCompleteTextViewAdapter() {
        boolean limit_selections = PreferenceActivity.getCompleteFromCurrentListOnlyFromPrefs(this);
        String listId = null;
        if (limit_selections) {
            listId = mListUri.getLastPathSegment();
        }

        // TODO: Optimize: This routine is called too often.
        if (debug)
            Log.d(TAG, "fill AutoCompleteTextViewAdapter");

        new AsyncTask<String, Integer, ArrayAdapter<String>>() {

            ArrayAdapter<String> adapter;

            @Override
            protected ArrayAdapter<String> doInBackground(String... params) {
                return fillAutoCompleteAdapter(params[0]);
            }

            @Override
            protected void onPostExecute(ArrayAdapter<String> adapter) {
                if (mEditText != null) {
                    // use result from doInBackground()
                    mEditText.setAdapter(adapter);
                }
            }

            private ArrayAdapter<String> fillAutoCompleteAdapter(String listId) {
                // Create list of item names
                Uri uri;
                String retCol;
                if (listId == null) {
                    uri = Items.CONTENT_URI;
                    retCol = Items.NAME;
                } else {
                    uri = Uri.parse("content://org.openintents.shopping/containsfull/list").buildUpon()
                            .appendPath(listId).build();
                    retCol = "items.name";
                }
                List<String> autocompleteItems = new LinkedList<String>();
                Cursor c = getContentResolver().query(uri, new String[] { retCol }, null, null, retCol + " asc");
                if (c != null) {
                    String lastitem = "";
                    while (c.moveToNext()) {
                        String newitem = c.getString(0);
                        // Only add items if they have not been added previously
                        // (list is sorted)
                        if (!newitem.equals(lastitem)) {
                            autocompleteItems.add(newitem);
                            lastitem = newitem;
                        }
                    }
                    c.close();
                }
                return new ArrayAdapter<String>(ShoppingActivity.this, android.R.layout.simple_dropdown_item_1line,
                        autocompleteItems);
            }

        }.execute(listId);
    }

    /**
     * Create list of tags.
     * 
     * Tags for notes can be comma-separated. Here we create a list of the
     * unique tags.
     * 
     * @param c
     * @return
     */
    String[] getTaglist() {
        return getTaglist(null);
    }

    /**
     * Create list of tags.
     * 
     * Tags for notes can be comma-separated. Here we create a list of the
     * unique tags.
     * 
     * @param c
     * @return
     */
    String[] getTaglist(String listId) {
        Cursor c;

        if (listId == null) {
            c = getContentResolver().query(ShoppingContract.Items.CONTENT_URI,
                    new String[] { ShoppingContract.Items.TAGS }, null, null,
                    ShoppingContract.Items.DEFAULT_SORT_ORDER);
        } else {
            Uri uri = Uri.parse("content://org.openintents.shopping/listtags").buildUpon().appendPath(listId)
                    .build();
            c = getContentResolver().query(uri, new String[] { ShoppingContract.ContainsFull.ITEM_TAGS }, null,
                    null, null);
        }

        // Create a set of all tags (every tag should only appear once).
        HashSet<String> tagset = new HashSet<String>();
        c.moveToPosition(-1);
        while (c.moveToNext()) {
            String tags = c.getString(0);
            if (tags != null) {
                // Split several tags in a line, separated by comma
                String[] smalltaglist = tags.split(",");
                for (String tag : smalltaglist) {
                    if (!tag.equals("")) {
                        tagset.add(tag.trim());
                    }
                }
            }
        }
        c.close();

        // Sort the list
        // 1. Convert HashSet to String list.
        ArrayList<String> list = new ArrayList<String>();
        list.addAll(tagset);
        // 2. Sort the String list
        Collections.sort(list);
        // 3. Convert it to String array
        return list.toArray(new String[0]);
    }

    /**
     * Tests whether the current list is shared via GTalk. (not local sharing!)
     * 
     * @return true if SHARE_CONTACTS contains the '@' character.
     */
    boolean isCurrentListShared() {
        long listId = getSelectedListId();
        if (listId < 0) {
            // No valid list - probably view is not active
            // and no item is selected.
            return false;
        }

        // mCursorListFilter has been set to correct position
        // by calling getSelectedListId(),
        // so we can read out further elements:
        // String shareName =
        // mCursorListFilter.getString(mStringListFilterSHARENAME);
        String recipients = mCursorShoppingLists.getString(mStringListFilterSHARECONTACTS);

        // If recipients contains the '@' symbol, it is shared.
        return recipients.contains("@");
    }

    // Handle the process of automatically updating enabled sensors:
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == MESSAGE_UPDATE_CURSORS) {
                mCursorShoppingLists.requery();

                if (mUpdating) {
                    sendMessageDelayed(obtainMessage(MESSAGE_UPDATE_CURSORS), mUpdateInterval);
                }

            }
        }
    };

    /**
     * Listens for intents for updates in the database.
     * 
     * @param context
     * @param intent
     */
    // TODO ???
    /*
     * public class ListIntentReceiver extends IntentReceiver {
     * 
     * public void onReceiveIntent(Context context, Intent intent) { String
     * action = intent.getAction(); Log.i(TAG, "ShoppingList received intent " +
     * action);
     * 
     * if (action.equals(OpenIntents.REFRESH_ACTION)) {
     * mCursorListFilter.requery();
     * 
     * } } }
     */
    /*
     * ListIntentReceiver mIntentReceiver;
     */

    /**
     * This method is called when the sending activity has finished, with the
     * result it supplied.
     * 
     * @param requestCode
     *            The original request code as given to startActivity().
     * @param resultCode
     *            From sending activity as per setResult().
     * @param data
     *            From sending activity as per setResult().
     * @param extras
     *            From sending activity as per setResult().
     * 
     * @see android.app.Activity#onActivityResult(int, int, java.lang.String,
     *      android.os.Bundle)
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (debug)
            Log.i(TAG, "ShoppingView: onActivityResult. ");

        IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
        if (scanResult != null) {
            final String contents = scanResult.getContents();
            if (contents != null) {
                long listId = mItemsView.getListId();
                if (listId == -1) { // no list selected - create one
                    createNewList(contents);
                    listId = mItemsView.getListId();
                }

                // make deleted items modified older
                ContentValues contentValues = new ContentValues();
                contentValues.put(Contains.MODIFIED_DATE, 0);
                final String listIdStr = String.valueOf(listId);
                getContentResolver().update(Contains.CONTENT_URI, contentValues, ContainsFull.STATUS + '='
                        + Status.REMOVED_FROM_LIST + " and " + ContainsFull.LIST_ID + '=' + listIdStr, null);

                Uri listUri = Uri.withAppendedPath(Lists.CONTENT_URI, listIdStr);
                contentValues = new ContentValues();
                contentValues.put(Lists.SHARE_ID, contents);
                int count = getContentResolver().update(listUri, contentValues, null, null);
                Assert.assertEquals(1, count);
            }
        } else if (requestCode == SUBACTIVITY_LIST_SHARE_SETTINGS) {
            if (debug)
                Log.i(TAG, "SUBACTIVITY_LIST_SHARE_SETTINGS");

            if (resultCode == RESULT_CANCELED) {
                // Don't do anything.
                if (debug)
                    Log.i(TAG, "RESULT_CANCELED");

            } else {
                // Broadcast the intent
                if (debug)
                    Log.i(TAG, "Broadcast intent.");

                // TODO ???
                /*
                 * Uri uri = Uri.parse(data);
                 */
                Uri uri = Uri.parse(data.getDataString());

                if (!mListUri.equals(uri)) {
                    Log.e(TAG, "Unexpected uri returned: Should be " + mListUri + " but was " + uri);
                    return;
                }

                // TODO ???
                Bundle extras = data.getExtras();

                String sharename = extras.getString(ShoppingContract.Lists.SHARE_NAME);
                String contacts = extras.getString(ShoppingContract.Lists.SHARE_CONTACTS);

                if (debug)
                    Log.i(TAG, "Received bundle: sharename: " + sharename + ", contacts: " + contacts);

                // Here we also send the current content of the list
                // to all recipients.
                // This could probably be optimized - by sending
                // content only to the new recipients, as the
                // old ones should be in sync already.
                // First delete all items in list
                /*
                 * mCursorItems.moveToPosition(-1); while
                 * (mCursorItems.moveToNext()) { String itemName = mCursorItems
                 * .getString(mStringItemsITEMNAME); Long status =
                 * mCursorItems.getLong(mStringItemsSTATUS); Log.i(TAG,
                 * "Update shared item. " + " recipients: " + contacts +
                 * ", shareName: " + sharename + ", item: " + itemName); // TODO
                 * ??? /* mGTalkSender.sendItemUpdate(contacts, sharename,
                 * itemName, itemName, status, status); / }
                 */
            }

        } else if (REQUEST_CODE_CATEGORY_ALTERNATIVE == requestCode) {
            if (debug)
                Log.d(TAG, "result received");
            if (RESULT_OK == resultCode) {
                if (debug)
                    Log.d(TAG, "result OK");
                // Check if any results have been returned:
                /*
                 * if ((data.getDataString() != null) &&
                 * (data.getDataString().startsWith
                 * (Shopping.Lists.CONTENT_URI.toString()))) { // We received a
                 * valid shopping list URI.
                 * 
                 * // Set current list to received list: mListUri =
                 * data.getData(); intent.setData(mListUri); }
                 */
                if (data.getExtras() != null) {
                    if (debug)
                        Log.d(TAG, "extras received");
                    getShoppingExtras(data);
                }
            }
        } else if (REQUEST_PICK_LIST == requestCode) {
            if (debug)
                Log.d(TAG, "result received");

            if (RESULT_OK == resultCode) {
                int position = mMoveItemPosition;
                if (mMoveItemPosition >= 0) {
                    moveItem(position, Integer.parseInt(data.getData().getLastPathSegment()));
                }
            }

            mMoveItemPosition = -1;
        }
    }

    public void changeList(int value) {

        int pos = mShoppingListsView.getSelectedItemPosition();
        // Temp- Due to architecture requirements of OS 3, the value can not be
        // passed directly
        if (pos == -1 && !usingListSpinner()) {
            try {
                pos = (Integer) mShoppingListsView.getTag();
                pos = mCursorShoppingLists.getCount() <= pos ? -1 : pos;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        int newPos;

        if (pos < 0) {
            // nothing selected - probably view is out of focus:
            // Do nothing.
            newPos = -1;
        } else if (pos == 0) {
            newPos = mShoppingListsView.getCount() - 1;
        } else if (pos == mShoppingListsView.getCount()) {
            newPos = 0;
        } else {
            newPos = pos + value;
        }
        setSelectedListPos(newPos);
    }

    /**
     * With the requirement of OS3, making an intermediary decision depending
     * upon the widget
     * 
     * @param adapter
     */
    private void setSpinnerListAdapter(ListAdapter adapter) {
        if (usingListSpinner()) {// Temp - restricted for OS3
            mShoppingListsView.setAdapter(adapter);
        } else {
            ShoppingListFilterFragment os3 = (ShoppingListFilterFragment) getSupportFragmentManager()
                    .findFragmentById(R.id.sidelist);
            os3.setAdapter(adapter);
        }
    }

    @Override
    public void onItemChanged() {
        mItemsView.mCursorItems.requery();
        fillAutoCompleteTextViewAdapter();
    }

}