com.clover.android.sdk.examples.InventoryTestActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.clover.android.sdk.examples.InventoryTestActivity.java

Source

/**
 * Copyright (C) 2015 Clover Network, Inc.
 *
 * 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 com.clover.android.sdk.examples;

import android.accounts.Account;
import android.app.Activity;
import android.app.LoaderManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.CursorLoader;
import android.content.Intent;
import android.content.Loader;
import android.content.ServiceConnection;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import android.widget.TextView;
import com.clover.sdk.util.CloverAccount;
import com.clover.sdk.util.CloverAuth;
import com.clover.sdk.v1.BindingException;
import com.clover.sdk.v1.ClientException;
import com.clover.sdk.v1.ForbiddenException;
import com.clover.sdk.v1.Intents;
import com.clover.sdk.v1.ResultStatus;
import com.clover.sdk.v1.ServiceCallback;
import com.clover.sdk.v1.ServiceConnector;
import com.clover.sdk.v1.ServiceException;
import com.clover.sdk.v3.inventory.Attribute;
import com.clover.sdk.v3.inventory.Category;
import com.clover.sdk.v3.inventory.Discount;
import com.clover.sdk.v3.inventory.IInventoryService;
import com.clover.sdk.v3.inventory.InventoryConnector;
import com.clover.sdk.v3.inventory.InventoryContract;
import com.clover.sdk.v3.inventory.InventoryIntent;
import com.clover.sdk.v3.inventory.Item;
import com.clover.sdk.v3.inventory.ItemGroup;
import com.clover.sdk.v3.inventory.Modifier;
import com.clover.sdk.v3.inventory.ModifierGroup;
import com.clover.sdk.v3.inventory.Option;
import com.clover.sdk.v3.inventory.OptionItem;
import com.clover.sdk.v3.inventory.PriceType;
import com.clover.sdk.v3.inventory.Tag;
import com.clover.sdk.v3.inventory.TaxRate;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.conn.params.ConnPerRouteBean;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;

public class InventoryTestActivity extends Activity {
    private static final String TAG = InventoryTestActivity.class.getSimpleName();

    private enum ConnectionMethod {
        BOUND_SERVICE, SERVICE_CONNECTOR, SERVICE_CONNECTOR_USING_CALLBACKS, WEB_SERVICE, CONTENT_PROVIDER_AND_SERVICE_WRAPPER
    }

    // use this if connected to web service or bound service
    private IInventoryService inventoryService;

    // this applies only to bound service
    private ServiceConnection serviceConnection;

    // this applies only to service wrapper
    private InventoryConnector inventoryConnector;

    // this applies only when using content provider
    private ItemLoader itemLoader = null;

    // this is used for all connection methods
    private boolean serviceIsBound = false;

    // Change this variable if you want to use a different connection method
    private ConnectionMethod connectionMethod = ConnectionMethod.SERVICE_CONNECTOR;

    private TextView resultText;
    private TextView statusText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_inventory_test);

        resultText = (TextView) findViewById(R.id.inventoryResult);
        statusText = (TextView) findViewById(R.id.inventoryStatus);
    }

    @Override
    protected void onResume() {
        super.onResume();
        switch (connectionMethod) {
        case SERVICE_CONNECTOR:
        case SERVICE_CONNECTOR_USING_CALLBACKS:
            connectToServiceConnector();
            break;
        case BOUND_SERVICE:
            connectToInventoryService();
            break;
        case WEB_SERVICE:
            connectToInventoryWebService();
            break;
        case CONTENT_PROVIDER_AND_SERVICE_WRAPPER:
            connectToInventoryContentProviderAndServiceWrapper();
            break;
        }
    }

    private void connectToInventoryContentProviderAndServiceWrapper() {
        inventoryConnector = new InventoryConnector(this, CloverAccount.getAccount(this),
                new ServiceConnector.OnServiceConnectedListener() {
                    @Override
                    public void onServiceConnected(ServiceConnector connector) {
                        serviceIsBound = true;
                    }

                    @Override
                    public void onServiceDisconnected(ServiceConnector connector) {
                        serviceIsBound = false;
                    }
                });
        inventoryConnector.connect();

        if (itemLoader != null) {
            itemLoader.restart();
        } else {
            itemLoader = new ItemLoader(this);
            itemLoader.init();
        }
    }

    private void connectToServiceConnector() {
        inventoryConnector = new InventoryConnector(this, CloverAccount.getAccount(this),
                new ServiceConnector.OnServiceConnectedListener() {
                    @Override
                    public void onServiceConnected(ServiceConnector connector) {
                        serviceIsBound = true;
                        statusText.setText("Connected to service via service wrapper");
                        if (connectionMethod == ConnectionMethod.SERVICE_CONNECTOR_USING_CALLBACKS) {
                            fetchItemsFromServiceConnectorUsingCallbacks();
                        } else {
                            fetchObjectsFromServiceConnector();
                        }
                    }

                    @Override
                    public void onServiceDisconnected(ServiceConnector connector) {
                        statusText.setText("Disconnected from service via wrapper");
                        serviceIsBound = false;
                    }
                });
        inventoryConnector.connect();
    }

    private void connectToInventoryWebService() {
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... params) {
                try {
                    Account account = CloverAccount.getAccount(InventoryTestActivity.this);
                    CloverAuth.AuthResult authResult = CloverAuth.authenticate(InventoryTestActivity.this, account);
                    String baseUrl = authResult.authData.getString(CloverAccount.KEY_BASE_URL);

                    if (authResult.authToken != null && baseUrl != null) {
                        CustomHttpClient httpClient = CustomHttpClient.getHttpClient();
                        String getNameUri = "/v2/merchant/name";
                        String url = baseUrl + getNameUri + "?access_token=" + authResult.authToken;
                        String result = httpClient.get(url);
                        JSONTokener jsonTokener = new JSONTokener(result);
                        JSONObject root = (JSONObject) jsonTokener.nextValue();
                        String merchantId = root.getString("merchantId");
                        inventoryService = new InventoryWebService(merchantId, authResult.authToken, baseUrl);
                    }
                } catch (Exception e) {
                    Log.e(TAG, "Error retrieving merchant info from server", e);
                }
                return null;
            }

            @Override
            protected void onPostExecute(Void aVoid) {
                if (inventoryService != null) {
                    statusText.setText("using web service");
                    serviceIsBound = true;
                    fetchItemsFromService();
                } else {
                    statusText.setText("failed to connect to web service");
                }
            }
        }.execute();
    }

    private void connectToInventoryService() {
        serviceConnection = new ServiceConnection() {
            public void onServiceConnected(ComponentName className, IBinder service) {
                inventoryService = IInventoryService.Stub.asInterface(service);
                serviceIsBound = true;
                statusText.setText("connected to AIDL service");
                fetchItemsFromService();
                createObjectsUsingService();
            }

            public void onServiceDisconnected(ComponentName className) {
                statusText.setText("disconnected from AIDL service");
                inventoryService = null;
                serviceIsBound = false;
            }
        };

        Account account = CloverAccount.getAccount(this);
        Intent intent = new Intent(InventoryIntent.ACTION_INVENTORY_SERVICE_V3);
        intent.putExtra(Intents.EXTRA_ACCOUNT, account);
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
    }

    // This should work for both bound AIDL service and web service
    private void fetchItemsFromService() {
        if (serviceIsBound && inventoryService != null) {
            new AsyncTask<Void, Void, Item>() {
                @Override
                protected Item doInBackground(Void... params) {
                    Item item = null;
                    try {
                        ResultStatus getItemsStatus = new ResultStatus();
                        List<Item> items = inventoryService.getItems(getItemsStatus);
                        Log.i(TAG, "Received result from getItems(): " + getItemsStatus);
                        if (getItemsStatus.isSuccess()) {
                            for (Item i : items) {
                                Log.i(TAG, "item = " + dumpItem(i));
                            }
                        }
                        if (items != null && items.size() > 0) {
                            String itemId = items.get(0).getId();
                            ResultStatus getItemStatus = new ResultStatus();
                            item = inventoryService.getItem(itemId, getItemStatus);
                            Log.i(TAG, "Received result from getItem(): " + getItemStatus);
                        }

                        ResultStatus resultStatus = new ResultStatus();
                        List<Category> categories = inventoryService.getCategories(resultStatus);
                        if (resultStatus.isSuccess()) {
                            for (Category category : categories) {
                                Log.i(TAG, "category = " + dumpCategory(category));
                            }
                        } else {
                            Log.i(TAG, "Couldn't retrieve categories: " + resultStatus);
                        }
                        List<TaxRate> taxRates = inventoryService.getTaxRates(resultStatus);
                        if (resultStatus.isSuccess()) {
                            for (TaxRate taxRate : taxRates) {
                                Log.i(TAG, "tax rate = " + dumpTaxRate(taxRate));
                            }
                        } else {
                            Log.i(TAG, "Couldn't retrieve tax rates: " + resultStatus);
                        }
                    } catch (RemoteException e) {
                        Log.e(TAG, "Error calling inventory service", e);
                    }
                    return item;
                }

                @Override
                protected void onPostExecute(Item result) {
                    displayItem(result);
                }
            }.execute();
        }
    }

    private void fetchItemsFromServiceConnectorUsingCallbacks() {
        if (serviceIsBound && inventoryConnector != null) {
            inventoryConnector.getItems(new ServiceCallback<List<Item>>() {

                @Override
                public void onServiceSuccess(List<Item> items, ResultStatus status) {
                    if (items != null && items.size() > 0) {
                        String itemId = items.get(0).getId();
                        inventoryConnector.getItem(itemId, new ServiceCallback<Item>() {
                            @Override
                            public void onServiceSuccess(Item result, ResultStatus resultStatus) {
                                displayItem(result);
                            }

                            @Override
                            public void onServiceFailure(ResultStatus resultStatus) {
                                Log.e(TAG, "Error calling getItem()");
                            }

                            @Override
                            public void onServiceConnectionFailure() {
                                Log.e(TAG, "Error calling getItem() - couldn't connect to service");
                            }
                        });
                    }
                }

                @Override
                public void onServiceFailure(ResultStatus status) {
                    Log.e(TAG, "Error calling getItems()");
                }

                @Override
                public void onServiceConnectionFailure() {
                    Log.e(TAG, "Error binding to inventory service");
                }
            });

            inventoryConnector.getCategories(new ServiceCallback<List<Category>>() {
                @Override
                public void onServiceSuccess(List<Category> result, ResultStatus resultStatus) {
                    for (Category category : result) {
                        Log.v(TAG, "category = " + dumpCategory(category));
                    }
                }

                @Override
                public void onServiceFailure(ResultStatus resultStatus) {
                    Log.e(TAG, "Error calling getCategories()");
                }
            });

            inventoryConnector.getTaxRates(new ServiceCallback<List<TaxRate>>() {
                @Override
                public void onServiceSuccess(List<TaxRate> result, ResultStatus status) {
                    for (TaxRate taxRate : result) {
                        Log.v(TAG, "tax rate = " + dumpTaxRate(taxRate));
                    }
                }

                @Override
                public void onServiceFailure(ResultStatus status) {
                    Log.e(TAG, "Error calling getCategories()");
                }
            });
        }
    }

    private void fetchObjectsFromServiceConnector() {
        if (serviceIsBound && inventoryConnector != null) {
            new AsyncTask<Void, Void, Item>() {
                @Override
                protected Item doInBackground(Void... params) {
                    Item item = null;
                    try {
                        List<String> items = inventoryConnector.getItemIds();
                        if (items != null) {
                            for (int i = 0; i < items.size(); i++) {
                                String itemId = items.get(i);
                                // just print out the first few to the console
                                if (i > 10) {
                                    break;
                                }
                                item = inventoryConnector.getItem(itemId);
                                Log.v(TAG, "item = " + dumpItem(item));
                            }

                            // fetch tax rates, categories, modifier groups and modifiers and just print them to log for now
                            List<Category> categories = inventoryConnector.getCategories();
                            if (categories != null) {
                                for (Category category : categories) {
                                    Log.v(TAG, "category = " + dumpCategory(category));
                                }
                            }
                            List<TaxRate> taxRates = inventoryConnector.getTaxRates();
                            if (taxRates != null) {
                                for (TaxRate taxRate : taxRates) {
                                    Log.v(TAG, "tax rate = " + dumpTaxRate(taxRate));
                                }
                            }
                            List<ModifierGroup> modifierGroups = inventoryConnector.getModifierGroups();
                            if (modifierGroups != null) {
                                for (ModifierGroup modifierGroup : modifierGroups) {
                                    Log.v(TAG, "modifier group = " + dumpModifierGroup(modifierGroup));
                                    List<Modifier> modifiers = inventoryConnector
                                            .getModifiers(modifierGroup.getId());
                                    if (modifiers != null) {
                                        for (Modifier modifier : modifiers) {
                                            Log.v(TAG, "modifier = " + dumpModifier(modifier));
                                        }
                                    }
                                }
                            }
                        }
                    } catch (ForbiddenException e) {
                        Log.e(TAG, "Auth exception", e);
                    } catch (ClientException e) {
                        Log.e(TAG, "Client exception", e);
                    } catch (ServiceException e) {
                        Log.e(TAG, "Service exception", e);
                    } catch (BindingException e) {
                        Log.e(TAG, "Error calling inventory service", e);
                    } catch (RemoteException e) {
                        Log.e(TAG, "Error calling inventory service", e);
                    }
                    return item;
                }

                @Override
                protected void onPostExecute(Item result) {
                    displayItem(result);
                }
            }.execute();
        }
    }

    private void createObjectsUsingService() {
        if (serviceIsBound && inventoryService != null) {
            new AsyncTask<Void, Void, List<Item>>() {
                @SuppressWarnings("ConstantConditions")
                @Override
                protected List<Item> doInBackground(Void... params) {
                    List<Item> items = new ArrayList<Item>();
                    try {
                        ResultStatus resultStatus = new ResultStatus();

                        // Attempt #1
                        Item newItem = new Item().setName("Test Item #1").setPrice(500L)
                                .setPriceType(PriceType.FIXED).setDefaultTaxRates(true);
                        newItem = inventoryService.createItem(newItem, resultStatus);
                        if (newItem != null) {
                            items.add(newItem);
                        }
                        Log.v(TAG,
                                "Result from createItem() #1 = " + resultStatus + ", item = " + dumpItem(newItem));

                        // Attempt #2, missing data (item name) should be caught by Item() constructor
                        try {
                            Item newItemFailure = new Item().setPrice(500L).setPriceType(PriceType.FIXED)
                                    .setDefaultTaxRates(true);
                            newItemFailure = inventoryService.createItem(newItemFailure, resultStatus);
                            if (newItemFailure != null) {
                                items.add(newItemFailure);
                            }
                            Log.v(TAG, "Result from createItem() #2 = " + resultStatus + ", item = "
                                    + dumpItem(newItemFailure));
                        } catch (Exception e) {
                            Log.v(TAG, "Result from createItem() #2, where we were missing required field: "
                                    + e.getMessage());
                        }

                        // Attempt #3, invalid data (price too low) should be caught by Item() constructor
                        try {
                            Item newItemFailure2 = new Item().setName("Test Item 3").setPrice(-500L)
                                    .setPriceType(PriceType.FIXED).setDefaultTaxRates(true);
                            newItemFailure2 = inventoryService.createItem(newItemFailure2, resultStatus);
                            if (newItemFailure2 != null) {
                                items.add(newItemFailure2);
                            }
                            Log.v(TAG, "Result from createItem() #3 = " + resultStatus + ", item = "
                                    + dumpItem(newItemFailure2));
                        } catch (Exception e) {
                            Log.v(TAG, "Result from createItem() #3, where we set an invalid value: "
                                    + e.getMessage());
                        }

                        // Attempt #4, using server's constructor
                        Item newItemWithId = new Item().setId("XXXXXXXXXXXXX").setName("Test Item 4").setPrice(500L)
                                .setPriceType(PriceType.FIXED).setDefaultTaxRates(true);
                        newItemWithId = inventoryService.createItem(newItemWithId, resultStatus);
                        if (newItemWithId != null) {
                            items.add(newItemWithId);
                        }
                        Log.v(TAG, "Result from createItem() #4 = " + resultStatus + ", item = "
                                + dumpItem(newItemWithId));

                        // Attempt #5, maliciously manipulating data to be invalid
                        String badJson = "{\"name\": \"Way Too Long Name..............................................................................................................................................\",\"price\": 600,\"priceType\": \"FIXED\",\"taxable\": true}";
                        Item newItemBadJson = new Item(badJson);
                        newItemBadJson = inventoryService.createItem(newItemBadJson, resultStatus);
                        if (newItemBadJson != null) {
                            items.add(newItemBadJson);
                        }
                        Log.v(TAG, "Result from createItem() #5 = " + resultStatus + ", item = " + newItemBadJson);
                    } catch (Exception e) {
                        Log.e(TAG, "Error calling inventory service", e);
                    }
                    return items;
                }

                @Override
                protected void onPostExecute(List<Item> results) {
                    for (Item result : results) {
                        displayItem(result);
                    }
                }
            }.execute();
        }
    }

    private void displayItem(Item item) {
        if (item != null) {
            String textViewContents = "";
            CharSequence text = resultText.getText();
            if (text != null) {
                textViewContents = text.toString();
            }
            resultText.setText(textViewContents + "\nitem = " + dumpItem(item));
        }
    }

    private String dumpItem(Item item) {
        return item != null
                ? String.format("%s{id=%s, name=%s, price=%d}", Item.class.getSimpleName(), item.getId(),
                        item.getName(), item.getPrice())
                : null;
    }

    private String dumpTaxRate(TaxRate taxRate) {
        return String.format("%s{id=%s, name=%s, rate=%d, is default=%b}", TaxRate.class.getSimpleName(),
                taxRate.getId(), taxRate.getName(), taxRate.getRate(), taxRate.getIsDefault());
    }

    private String dumpModifier(Modifier modifier) {
        return String.format("%s{id=%s, name=%s}", Modifier.class.getSimpleName(), modifier.getId(),
                modifier.getName());
    }

    private String dumpModifierGroup(ModifierGroup modifierGroup) {
        return String.format("%s{id=%s, name=%s}", ModifierGroup.class.getSimpleName(), modifierGroup.getId(),
                modifierGroup.getName());
    }

    private String dumpCategory(Category category) {
        return String.format("%s{id=%s, name=%s, sort order=%d}", Category.class.getSimpleName(), category.getId(),
                category.getName(), category.getSortOrder());
    }

    @Override
    protected void onStop() {
        super.onStop();
    }

    @Override
    protected void onPause() {
        switch (connectionMethod) {
        case SERVICE_CONNECTOR:
        case SERVICE_CONNECTOR_USING_CALLBACKS:
            if (inventoryConnector != null) {
                inventoryConnector.disconnect();
                inventoryConnector = null;
            }
            break;
        case BOUND_SERVICE:
            if (serviceIsBound && serviceConnection != null) {
                unbindService(serviceConnection);
                serviceIsBound = false;
            }
            break;
        case WEB_SERVICE:
            break;
        case CONTENT_PROVIDER_AND_SERVICE_WRAPPER:
            // destroy cursor loader
            if (itemLoader != null) {
                itemLoader.destroy();
            }
            break;
        }

        if (serviceIsBound && serviceConnection != null) {
            unbindService(serviceConnection);
            serviceIsBound = false;
        }

        super.onPause();
    }

    private class ItemLoader implements LoaderManager.LoaderCallbacks<Cursor> {

        private static final int ITEM_LOADER_ID = 1;

        private final Activity activity;
        private final Account account;

        public ItemLoader(Activity activity) {
            this.account = CloverAccount.getAccount(activity);
            this.activity = activity;
        }

        public void init() {
            activity.getLoaderManager().initLoader(ITEM_LOADER_ID, null, this);
        }

        public void destroy() {
            activity.getLoaderManager().destroyLoader(ITEM_LOADER_ID);
        }

        @Override
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            switch (id) {
            case ITEM_LOADER_ID:
                return new CursorLoader(activity, InventoryContract.Item.contentUriWithAccount(account), null, null,
                        null, InventoryContract.Item.NAME);
            }
            throw new IllegalArgumentException("Unknown loader ID");
        }

        @Override
        public final void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
            switch (loader.getId()) {
            case ITEM_LOADER_ID:
                statusText.setText("Connected to content provider");
                // We're only going to get the first result from the cursor but most usages would presumably
                // use a CursorAdapter or similar mechanism to fetch some range of data from the cursor
                if (cursor != null) {
                    if (cursor.moveToFirst()) {
                        try {
                            Item item = getItemFromCursor(cursor);
                            Log.d(TAG, "Retrieved item from cursor: " + dumpItem(item));
                            // now fetch the full item with modifiers, categories, tax rates, etc. from the service
                            if (serviceIsBound && inventoryConnector != null) {
                                inventoryConnector.getItem(item.getId(), new ServiceCallback<Item>() {
                                    @Override
                                    public void onServiceSuccess(Item result, ResultStatus status) {
                                        displayItem(result);
                                    }

                                    @Override
                                    public void onServiceFailure(ResultStatus status) {
                                        Log.e(TAG, "Error calling getItem() on service wrapper");
                                    }

                                    @Override
                                    public void onServiceConnectionFailure() {
                                        Log.e(TAG, "Error binding to service wrapper");
                                    }
                                });
                            }
                        } catch (Exception e) {
                            Log.e(TAG, "Error retrieving item", e);
                        }
                    }
                }
                break;
            }
        }

        @Override
        public void onLoaderReset(Loader<Cursor> cursorLoader) {
            // we're not binding to an adapter or hanging on to the cursor, so no need to do anything here
        }

        public void restart() {
            activity.getLoaderManager().restartLoader(ITEM_LOADER_ID, null, this);
        }

        @SuppressWarnings("ConstantConditions")
        private Item getItemFromCursor(Cursor cursor) throws JSONException {
            int uuidIndex = cursor.getColumnIndex(InventoryContract.Item.ID);
            int nameIndex = cursor.getColumnIndex(InventoryContract.Item.NAME);
            int alternateNameIndex = cursor.getColumnIndex(InventoryContract.Item.ALTERNATE_NAME);
            int codeIndex = cursor.getColumnIndex(InventoryContract.Item.CODE);
            int unitNameIndex = cursor.getColumnIndex(InventoryContract.Item.UNIT_NAME);
            int priceIndex = cursor.getColumnIndex(InventoryContract.Item.PRICE);
            int priceTypeIndex = cursor.getColumnIndex(InventoryContract.Item.PRICE_TYPE);
            int defaultTaxRatesIndex = cursor.getColumnIndex(InventoryContract.Item.DEFAULT_TAX_RATES);

            String id = null, name = null, alternateName = null, unitName = null, code = null;
            Integer priceTypeInt, defaultTaxRatesInt;
            PriceType priceType = null;
            Long price = null;
            Boolean defaultTaxRates = null;
            Long cost = null; // not currently stored in local db

            if (uuidIndex >= 0) {
                id = cursor.getString(uuidIndex);
            }
            if (nameIndex >= 0) {
                name = cursor.getString(nameIndex);
            }
            if (alternateNameIndex >= 0) {
                alternateName = cursor.getString(alternateNameIndex);
            }
            if (codeIndex >= 0) {
                code = cursor.getString(codeIndex);
            }
            if (unitNameIndex >= 0) {
                unitName = cursor.getString(unitNameIndex);
            }
            if (priceIndex >= 0) {
                price = cursor.getLong(priceIndex);
            }
            if (priceTypeIndex >= 0) {
                priceTypeInt = cursor.getInt(priceTypeIndex);
                priceType = PriceType.values()[priceTypeInt];
            }
            if (defaultTaxRatesIndex >= 0) {
                defaultTaxRatesInt = cursor.getInt(defaultTaxRatesIndex);
                defaultTaxRates = defaultTaxRatesInt != null && defaultTaxRatesInt == 1;
            }

            return new Item().setId(id).setName(name).setAlternateName(alternateName).setCode(code).setPrice(price)
                    .setPriceType(priceType).setDefaultTaxRates(defaultTaxRates).setUnitName(unitName)
                    .setCost(cost);
        }
    }

    static class InventoryWebService implements IInventoryService {
        private static final String TAG = InventoryWebService.class.getSimpleName();

        @Override
        public List<Discount> getDiscounts(ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement getDiscounts()");
        }

        @Override
        public Discount getDiscount(String discountId, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement getDiscount()");
        }

        @Override
        public Discount createDiscount(Discount discount, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement createDiscount()");
        }

        @Override
        public void updateDiscount(Discount discount, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement updateDiscount()");
        }

        @Override
        public void deleteDiscount(String discountId, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement deleteDiscount()");
        }

        @Override
        public void addItemToCategory(String itemId, String categoryId, ResultStatus resultStatus)
                throws RemoteException {
            throw new UnsupportedOperationException("Need to implement addItemToCategory()");
        }

        @Override
        public void removeItemFromCategory(String itemId, String categoryId, ResultStatus resultStatus)
                throws RemoteException {
            throw new UnsupportedOperationException("Need to implement removeItemFromCategory()");
        }

        @Override
        public void moveItemInCategoryLayout(String itemId, String categoryId, int direction,
                ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement moveItemInCategoryLayout()");
        }

        @Override
        public void assignModifierGroupToItem(String modifierGroupId, String itemId, ResultStatus resultStatus)
                throws RemoteException {
            throw new UnsupportedOperationException("Need to implement assignModifierGroupToItem()");
        }

        @Override
        public void removeModifierGroupFromItem(String modifierGroupId, String itemId, ResultStatus resultStatus)
                throws RemoteException {
            throw new UnsupportedOperationException("Need to implement removeModifierGroupFromItem()");
        }

        @Override
        public TaxRate createTaxRate(TaxRate taxRate, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement createTaxRate()");
        }

        @Override
        public void updateTaxRate(TaxRate taxRate, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement updateTaxRate()");
        }

        @Override
        public void deleteTaxRate(String taxRateId, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement deleteTaxRate()");
        }

        private String merchantId;
        private String accessToken;
        private String baseUrl;

        public InventoryWebService(String merchantId, String accessToken, String baseUrl) {
            this.merchantId = merchantId;
            this.accessToken = accessToken;
            this.baseUrl = baseUrl;
        }

        @Override
        public List<Item> getItems(ResultStatus resultStatus) throws RemoteException {
            String uri = "/v2/merchant/" + merchantId + "/inventory/items";
            return getArrayResults(Item.class, uri, "items", resultStatus);
        }

        @Override
        public List<Item> getItemsWithCategories(ResultStatus resultStatus) throws RemoteException {
            String uri = "/v2/merchant/" + merchantId + "/inventory/items_with_categories";
            return getArrayResults(Item.class, uri, "items", resultStatus);
        }

        @Override
        public List<String> getItemIds(ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("getItemIds() not supported through web service API");
        }

        @Override
        public Item getItem(String itemId, ResultStatus resultStatus) throws RemoteException {
            String uri = "/v2/merchant/" + merchantId + "/inventory/items/" + itemId;
            return getResult(Item.class, uri, "item", resultStatus);
        }

        @Override
        public Item getItemWithCategories(String itemId, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException(
                    "getItemWithCategories() not supported through web service API");
        }

        @Override
        public Item createItem(Item item, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement createItem()");
        }

        @Override
        public void updateItem(Item item, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement updateItem()");
        }

        @Override
        public void deleteItem(String itemId, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement deleteItem()");
        }

        @Override
        public List<TaxRate> getTaxRatesForItem(String itemId, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("getTaxRatesForItem() not supported through web service API");
        }

        @Override
        public void assignTaxRatesToItem(String itemId, List<String> taxRates, ResultStatus resultStatus)
                throws RemoteException {
            throw new UnsupportedOperationException("Need to implement assignTaxRatesToItem()");
        }

        @Override
        public void removeTaxRatesFromItem(String itemId, List<String> taxRates, ResultStatus resultStatus)
                throws RemoteException {
            throw new UnsupportedOperationException("Need to implement removeTaxRatesFromItem()");
        }

        @Override
        public TaxRate getTaxRate(String taxRateId, ResultStatus resultStatus) throws RemoteException {
            String uri = "/v2/merchant/" + merchantId + "/tax_rates/" + taxRateId;
            return getResult(TaxRate.class, uri, "taxRate", resultStatus);
        }

        @Override
        public List<TaxRate> getTaxRates(ResultStatus resultStatus) throws RemoteException {
            String uri = "/v2/merchant/" + merchantId + "/tax_rates";
            return getArrayResults(TaxRate.class, uri, "taxRates", resultStatus);
        }

        @Override
        public List<Category> getCategories(ResultStatus resultStatus) throws RemoteException {
            String uri = "/v2/merchant/" + merchantId + "/inventory/categories";
            return getArrayResults(Category.class, uri, "categories", resultStatus);
        }

        private <T> T getResult(Class<T> dataClass, String uri, String jsonFieldName, ResultStatus resultStatus)
                throws RemoteException {
            CustomHttpClient httpClient = CustomHttpClient.getHttpClient();

            try {
                String url = baseUrl + uri + "?access_token=" + accessToken;
                String result = httpClient.get(url);
                if (!TextUtils.isEmpty(result)) {
                    JSONTokener jsonTokener = new JSONTokener(result);
                    JSONObject root = (JSONObject) jsonTokener.nextValue();
                    JSONObject jsonObject = root.getJSONObject(jsonFieldName);
                    if (jsonObject != null) {
                        Constructor<T> constructor = dataClass.getConstructor(JSONObject.class);
                        return constructor.newInstance(jsonObject);
                    }
                }
            } catch (Exception e) {
                resultStatus.setStatus(ResultStatus.SERVICE_ERROR, "Error retrieving result from server");
                Log.e(TAG, "Error retrieving result from server", e);
                throw new RemoteException();
            }

            resultStatus.setStatusCode(ResultStatus.NOT_FOUND);
            return null;
        }

        private <T> List<T> getArrayResults(Class<T> dataClass, String uri, String jsonFieldName,
                ResultStatus resultStatus) throws RemoteException {
            CustomHttpClient httpClient = CustomHttpClient.getHttpClient();

            try {
                List<T> results = new ArrayList<T>();
                String url = baseUrl + uri + "?access_token=" + accessToken;
                String result = httpClient.get(url);
                if (!TextUtils.isEmpty(result)) {
                    JSONTokener jsonTokener = new JSONTokener(result);
                    JSONObject root = (JSONObject) jsonTokener.nextValue();
                    JSONArray jsonArray = root.getJSONArray(jsonFieldName);
                    for (int i = 0; i < jsonArray.length(); i++) {
                        JSONObject jsonObject = jsonArray.getJSONObject(i);
                        Constructor<T> constructor = dataClass.getConstructor(JSONObject.class);
                        T obj = constructor.newInstance(jsonObject);
                        results.add(obj);
                    }
                }
                resultStatus.setStatusCode(ResultStatus.OK);
                return results;
            } catch (Exception e) {
                resultStatus.setStatus(ResultStatus.SERVICE_ERROR, "Error retrieving result from server");
                Log.e(TAG, "Error retrieving result from server", e);
                throw new RemoteException();
            }
        }

        @Override
        public Category createCategory(Category category, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement createCategory()");
        }

        @Override
        public void updateCategory(Category category, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement updateCategory()");
        }

        @Override
        public void deleteCategory(String categoryId, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement deleteCategory()");
        }

        @Override
        public List<ModifierGroup> getModifierGroups(ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement getModifierGroups()");
        }

        @Override
        public ModifierGroup createModifierGroup(ModifierGroup group, ResultStatus resultStatus)
                throws RemoteException {
            throw new UnsupportedOperationException("Need to implement createModifierGroup()");
        }

        @Override
        public void updateModifierGroup(ModifierGroup group, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement updateModifierGroup()");
        }

        @Override
        public void deleteModifierGroup(String groupId, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement deleteModifierGroup()");
        }

        @Override
        public List<Modifier> getModifiers(String modifierGroupId, ResultStatus resultStatus)
                throws RemoteException {
            throw new UnsupportedOperationException("Need to implement getModifiers()");
        }

        @Override
        public Modifier createModifier(String modifierGroupId, Modifier modifier, ResultStatus resultStatus)
                throws RemoteException {
            throw new UnsupportedOperationException("Need to implement createModifier()");
        }

        @Override
        public void updateModifier(Modifier modifier, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement updateModifier()");
        }

        @Override
        public void deleteModifier(String modifierId, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement deleteModifier()");
        }

        @Override
        public List<ModifierGroup> getModifierGroupsForItem(String itemId, ResultStatus resultStatus)
                throws RemoteException {
            throw new UnsupportedOperationException("Need to implement getModifierGroupsForItem()");
        }

        @Override
        public List<Tag> getTags(ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement getTags()");
        }

        @Override
        public Tag getTag(String tagId, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement getTag()");
        }

        @Override
        public Tag createTag(Tag tag, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement createTag()");
        }

        @Override
        public void updateTag(Tag tag, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement updateTag()");
        }

        @Override
        public void deleteTag(String tagId, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement deleteTag()");
        }

        @Override
        public List<Tag> getTagsForItem(String itemId, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement getTagsForItem()");
        }

        @Override
        public void assignTagsToItem(String itemId, List<String> tags, ResultStatus resultStatus)
                throws RemoteException {
            throw new UnsupportedOperationException("Need to implement assignTagsToItem()");
        }

        @Override
        public void removeTagsFromItem(String itemId, List<String> tags, ResultStatus resultStatus)
                throws RemoteException {
            throw new UnsupportedOperationException("Need to implement removeTagsFromItem()");
        }

        @Override
        public void assignItemsToTag(String tagId, List<String> items, ResultStatus resultStatus)
                throws RemoteException {
            throw new UnsupportedOperationException("Need to implement assignItemsToTag()");
        }

        @Override
        public void removeItemsFromTag(String tagId, List<String> items, ResultStatus resultStatus)
                throws RemoteException {
            throw new UnsupportedOperationException("Need to implement removeItemsFromTag()");
        }

        @Override
        public List<Tag> getTagsForPrinter(String printerMac, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement getTagsForPrinter()");
        }

        @Override
        public void assignTagsToPrinter(String printerMac, List<String> tags, ResultStatus resultStatus)
                throws RemoteException {
            throw new UnsupportedOperationException("Need to implement assignTagsToPrinter()");
        }

        @Override
        public void removeTagsFromPrinter(String printerMac, List<String> tags, ResultStatus resultStatus)
                throws RemoteException {
            throw new UnsupportedOperationException("Need to implement removeTagsFromPrinter()");
        }

        @Override
        public void updateModifierSortOrder(String modifierGroupId, List<String> modifierIds,
                ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement updateModifierSortOrder()");
        }

        @Override
        public List<Attribute> getAttributes(ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement getAttributes()");
        }

        @Override
        public Attribute getAttribute(String attributeId, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement getAttribute()");
        }

        @Override
        public Attribute createAttribute(Attribute attribute, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement createAttribute()");
        }

        @Override
        public void updateAttribute(Attribute attribute, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement updateAttribute()");
        }

        @Override
        public void deleteAttribute(String attributeId, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement deleteAttribute()");
        }

        @Override
        public List<Option> getOptions(ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement getOptions()");
        }

        @Override
        public Option getOption(String optionId, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement getOption()");
        }

        @Override
        public Option createOption(Option option, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement createOption()");
        }

        @Override
        public void updateOption(Option option, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement updateOption()");
        }

        @Override
        public void deleteOption(String optionId, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement deleteOption()");
        }

        @Override
        public List<Option> getOptionsForItem(String itemId, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement getOptionsForItem()");
        }

        @Override
        public void assignOptionsToItem(String itemId, List<String> optionIds, ResultStatus resultStatus)
                throws RemoteException {
            throw new UnsupportedOperationException("Need to implement assignOptionsToItem()");
        }

        @Override
        public void removeOptionsFromItem(String itemId, List<String> optionIds, ResultStatus resultStatus)
                throws RemoteException {
            throw new UnsupportedOperationException("Need to implement removeOptionsFromItem()");
        }

        @Override
        public void updateItemStock(String itemId, long stockCount, ResultStatus resultStatus)
                throws RemoteException {
            throw new UnsupportedOperationException("Need to implement updateItemStock()");
        }

        @Override
        public void removeItemStock(String itemId, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement removeItemStock()");
        }

        @Override
        public ItemGroup getItemGroup(String itemGroupId, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement getItemGroup()");
        }

        @Override
        public ItemGroup createItemGroup(ItemGroup itemGroup, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement createItemGroup()");
        }

        @Override
        public void updateItemGroup(ItemGroup itemGroup, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement updateItemGroup()");
        }

        @Override
        public void deleteItemGroup(String itemGroupId, ResultStatus resultStatus) throws RemoteException {
            throw new UnsupportedOperationException("Need to implement deleteItemGroup()");
        }

        @Override
        public void updateItemStockQuantity(String itemId, double quantity, ResultStatus resultStatus)
                throws RemoteException {
            throw new UnsupportedOperationException("Need to implement updateItemStockQuantity()");
        }

        @Override
        public IBinder asBinder() {
            throw new UnsupportedOperationException("Not a real Android service");
        }
    }

    static class CustomHttpClient extends DefaultHttpClient {
        private static final int CONNECT_TIMEOUT = 60000;
        private static final int READ_TIMEOUT = 60000;
        private static final int MAX_TOTAL_CONNECTIONS = 5;
        private static final int MAX_CONNECTIONS_PER_ROUTE = 3;
        private static final int SOCKET_BUFFER_SIZE = 8192;
        private static final boolean FOLLOW_REDIRECT = false;
        private static final boolean STALE_CHECKING_ENABLED = true;
        private static final String CHARSET = HTTP.UTF_8;
        private static final HttpVersion HTTP_VERSION = HttpVersion.HTTP_1_1;
        private static final String USER_AGENT = "CustomHttpClient"; // + version

        public static CustomHttpClient getHttpClient() {
            CustomHttpClient httpClient = new CustomHttpClient();
            final HttpParams params = httpClient.getParams();
            HttpProtocolParams.setUserAgent(params, USER_AGENT);
            HttpProtocolParams.setContentCharset(params, CHARSET);
            HttpProtocolParams.setVersion(params, HTTP_VERSION);
            HttpClientParams.setRedirecting(params, FOLLOW_REDIRECT);
            HttpConnectionParams.setConnectionTimeout(params, CONNECT_TIMEOUT);
            HttpConnectionParams.setSoTimeout(params, READ_TIMEOUT);
            HttpConnectionParams.setSocketBufferSize(params, SOCKET_BUFFER_SIZE);
            HttpConnectionParams.setStaleCheckingEnabled(params, STALE_CHECKING_ENABLED);
            ConnManagerParams.setTimeout(params, CONNECT_TIMEOUT);
            ConnManagerParams.setMaxTotalConnections(params, MAX_TOTAL_CONNECTIONS);
            ConnManagerParams.setMaxConnectionsPerRoute(params, new ConnPerRouteBean(MAX_CONNECTIONS_PER_ROUTE));

            return httpClient;
        }

        public String get(String url) throws IOException, HttpException {
            String result;
            HttpGet request = new HttpGet(url);
            HttpResponse response = execute(request);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == HttpStatus.SC_OK) {
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    result = EntityUtils.toString(entity);
                } else {
                    throw new HttpException("Received empty body from HTTP response");
                }
            } else {
                throw new HttpException("Received non-OK status from server: " + response.getStatusLine());
            }
            return result;
        }

        @SuppressWarnings("unused")
        public String post(String url, String body) throws IOException, HttpException {
            String result;
            HttpPost request = new HttpPost(url);
            HttpEntity bodyEntity = new StringEntity(body);
            request.setEntity(bodyEntity);
            HttpResponse response = execute(request);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == HttpStatus.SC_OK) {
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    result = EntityUtils.toString(entity);
                } else {
                    throw new HttpException("Received empty body from HTTP response");
                }
            } else {
                throw new HttpException("Received non-OK status from server: " + response.getStatusLine());
            }
            return result;
        }

    }
}