public class BuildingEntry extends CivilopediaEntry {
    private static final String TAG = BuildingEntry.class.getName();

    private static final String BUILDING_TABLE = "building";
    private static final String BUILDING_COL_ID = ID;
    private static final String BUILDING_COL_NAME = NAME;
    private static final String BUILDING_COL_CIVILOPEDIA = "civilopedia";
    private static final String BUILDING_COL_HELP = "help";
    private static final String BUILDING_COL_STRATEGY = "strategy";
    private static final String BUILDING_COL_COST = "cost";
    private static final String BUILDING_COL_FAITH_COST = "faith_cost";
    private static final String BUILDING_COL_MAINTENANCE = "maintenance";
    private static final String BUILDING_COL_TECHNOLOGY = "technology";
    private static final String BUILDING_COL_TYPE = "type";
    private static final String BUILDING_COL_SORT_ORDER = "sort_order";
    private static final String [] BUILDING_COLS = new String [] {
        BUILDING_COL_ID,
        BUILDING_COL_NAME,
        BUILDING_COL_CIVILOPEDIA,
        BUILDING_COL_HELP,
        BUILDING_COL_STRATEGY,
        BUILDING_COL_COST,
        BUILDING_COL_FAITH_COST,
        BUILDING_COL_MAINTENANCE,
        BUILDING_COL_TECHNOLOGY,
        BUILDING_COL_TYPE,
        BUILDING_COL_SORT_ORDER,
    };

    // Members
    private String mKey;
    private String mName;
    private String mGroup;
    private String mStrategy;
    private String mCivilopedia;
    private String mHelp;
    private int mCost;  // in "hammers"
    private int mFaithCost;
    private int mMaintenance;

    public BuildingEntry() {
    }

    @Override
    public String getKey() {
        return mKey;
    }

    public void setKey(String key) {
        this.mKey = key;
    }

    @Override
    public String getName() {
        return mName;
    }

    public void setName(String name) {
        this.mName = name;
    }

    @Override
    public String getGroup() {
        return mGroup;
    }

    public void setGroup(String group) {
        mGroup = group;
    }

    public String getStrategy() {
        return mStrategy;
    }

    public void setStrategy(String strategy) {
        this.mStrategy = strategy;
    }

    public String getCivilopedia() {
        return mCivilopedia;
    }

    public void setCivilopedia(String civilopedia) {
        this.mCivilopedia = civilopedia;
    }

    public String getHelp() {
        return mHelp;
    }

    public void setHelp(String help) {
        this.mHelp = help;
    }

    public int getCost() {
        return mCost;
    }

    public void setCost(int cost) {
        this.mCost = cost;
    }

    public int getFaithCost() {
        return mFaithCost;
    }

    public void setFaithCost(int faithCost) {
        this.mFaithCost = faithCost;
    }

    public int getMaintenance() {
        return mMaintenance;
    }

    public void setMaintenance(int maintenance) {
        this.mMaintenance = maintenance;
    }

    public static ArrayList<String> getGroups(Context context) {
        ArrayList<String> result = new ArrayList<String>();
        SQLiteDatabase conn = null;
        Cursor cursor = null;
        try {
            conn = new CivilopediaDatabaseHelper(context).openConnection();
            cursor = conn.query(BUILDING_TABLE, new String [] { BUILDING_COL_TYPE }, null, null, BUILDING_COL_TYPE,
                    null, BUILDING_COL_SORT_ORDER + "," + BUILDING_COL_NAME);

            cursor.moveToFirst();
            while (!cursor.isAfterLast()) {
                result.add(cursor.getString(0));
                cursor.moveToNext();
            }
        } catch (IOException e) {
            Log.e(TAG, "Failed to get building groups: " + e.getLocalizedMessage());
        } finally {
            if (null != cursor) {
                cursor.close();
            }
            if (null != conn) {
                conn.close();
            }
        }

        return result;
    }

    public static ArrayList<CivilopediaEntry> getEntries(Context context) {
        ArrayList<CivilopediaEntry> result = new ArrayList<CivilopediaEntry>();
        SQLiteDatabase conn = null;
        Cursor cursor = null;
        try {
            conn = new CivilopediaDatabaseHelper(context).openConnection();
            cursor = conn.query(BUILDING_TABLE, BUILDING_COLS, null, null, null,
                    null, BUILDING_COL_NAME + "," + BUILDING_COL_ID);

            cursor.moveToFirst();
            while (!cursor.isAfterLast()) {
                BuildingEntry building = new BuildingEntry();
                building.setKey(cursor.getString(0));
                building.setName(cursor.getString(1));
                building.setCivilopedia(cursor.getString(2));
                building.setHelp(cursor.getString(3));
                building.setStrategy(cursor.getString(4));
                building.setCost(cursor.getInt(5));
                building.setFaithCost(cursor.getInt(6));
                building.setMaintenance(cursor.getInt(7));
                building.setGroup(cursor.getString(9));
                result.add(building);
                cursor.moveToNext();
            }
        } catch (IOException e) {
            Log.e(TAG, "Failed to get buildings: " + e.getLocalizedMessage());
        } finally {
            if (null != cursor) {
                cursor.close();
            }
            if (null != conn) {
                conn.close();
            }
        }

        return result;
    }

    public static BuildingEntry getBuildingById(Context context, String id) {
        BuildingEntry building = new BuildingEntry();
        SQLiteDatabase conn = null;
        Cursor cursor = null;

        try {
            conn = new CivilopediaDatabaseHelper(context).openConnection();
            cursor = conn.query(BUILDING_TABLE, BUILDING_COLS,
                    BUILDING_COL_ID + "=?", new String [] { id },
                    null, null, null);
            cursor.moveToFirst();
            if (!cursor.isAfterLast()) {
                building.setKey(cursor.getString(0));
                building.setName(cursor.getString(1));
                building.setCivilopedia(cursor.getString(2));
                building.setHelp(cursor.getString(3));
                building.setStrategy(cursor.getString(4));
                building.setCost(cursor.getInt(5));
                building.setFaithCost(cursor.getInt(6));
                building.setMaintenance(cursor.getInt(7));
                building.setGroup(cursor.getString(9));
            }
        } catch (IOException e) {
            Log.e(TAG, "Failed to get building (" + id + "): " + e.getLocalizedMessage());
        } finally {
            if (null != cursor) {
                cursor.close();
            }
            if (null != conn) {
                conn.close();
            }
        }
        return building;
    }
} public abstract class CivilopediaEntry {
    public static final String ID = "_id";
    public static final String NAME = "name";
    public static final String GROUP = "group";

    public CivilopediaEntry() {
    }

    public abstract String getKey();
    public abstract String getName();
    public abstract String getGroup();
} public class PolicyEntry extends CivilopediaEntry {
    private static final String TAG = PolicyEntry.class.getName();

    private static final String POLICY_TABLE = "policy";
    private static final String POLICY_COL_ID = ID;
    private static final String POLICY_COL_NAME = NAME;
    private static final String POLICY_COL_CIVILOPEDIA = "civilopedia";
    private static final String POLICY_COL_HELP = "help";
    private static final String POLICY_COL_TYPE = "type";
    private static final String POLICY_COL_SORT_ORDER = "sort_order";
    private static final String [] POLICY_COLS = new String [] {
        POLICY_COL_ID,
        POLICY_COL_NAME,
        POLICY_COL_CIVILOPEDIA,
        POLICY_COL_HELP,
        POLICY_COL_TYPE,
    };

    // Members
    private String mKey;
    private String mName;
    private String mGroup;
    private String mCivilopedia;
    private String mHelp;

    public PolicyEntry() {
    }

    @Override
    public String getKey() {
        return mKey;
    }

    public void setKey(String key) {
        this.mKey = key;
    }

    @Override
    public String getName() {
        return mName;
    }

    public void setName(String name) {
        this.mName = name;
    }

    @Override
    public String getGroup() {
        return mGroup;
    }

    public void setGroup(String group) {
        mGroup = group;
    }

    public String getCivilopedia() {
        return mCivilopedia;
    }

    public void setCivilopedia(String civilopedia) {
        this.mCivilopedia = civilopedia;
    }

    public String getHelp() {
        return mHelp;
    }

    public void setHelp(String help) {
        this.mHelp = help;
    }

    public static ArrayList<String> getGroups(Context context) {
        ArrayList<String> result = new ArrayList<String>();
        SQLiteDatabase conn = null;
        Cursor cursor = null;
        try {
            // Gets distinct types sorted by the sort order
            conn = new CivilopediaDatabaseHelper(context).openConnection();
            cursor = conn.query(POLICY_TABLE, new String [] { POLICY_COL_TYPE }, null, null,
                    POLICY_COL_TYPE, null, POLICY_COL_SORT_ORDER + "," + POLICY_COL_NAME);

            cursor.moveToFirst();
            while (!cursor.isAfterLast()) {
                result.add(cursor.getString(0));
                cursor.moveToNext();
            }
        } catch (IOException e) {
            Log.e(TAG, "Failed to get policy groups: " + e.getLocalizedMessage());
        } finally {
            if (null != cursor) {
                cursor.close();
            }
            if (null != conn) {
                conn.close();
            }
        }
        return result;
    }

    public static ArrayList<CivilopediaEntry> getEntries(Context context) {
        ArrayList<CivilopediaEntry> result = new ArrayList<CivilopediaEntry>();
        SQLiteDatabase conn = null;
        Cursor cursor = null;
        try {
            conn = new CivilopediaDatabaseHelper(context).openConnection();
            cursor = conn.query(POLICY_TABLE, POLICY_COLS, null, null,
                    null, null, POLICY_COL_NAME + "," + POLICY_COL_ID);

            cursor.moveToFirst();
            while (!cursor.isAfterLast()) {
                PolicyEntry policy = new PolicyEntry();
                policy.setKey(cursor.getString(0));
                policy.setName(cursor.getString(1));
                policy.setCivilopedia(cursor.getString(2));
                policy.setHelp(cursor.getString(3));
                policy.setGroup(cursor.getString(4));
                result.add(policy);
                cursor.moveToNext();
            }
        } catch (IOException e) {
            Log.e(TAG, "Failed to get policies: " + e.getLocalizedMessage());
        } finally {
            if (null != cursor) {
                cursor.close();
            }
            if (null != conn) {
                conn.close();
            }
        }

        return result;
    }

    public static PolicyEntry getPolicyById(Context context, String key) {
        PolicyEntry policy = new PolicyEntry();
        SQLiteDatabase conn = null;
        Cursor cursor = null;

        try {
            conn = new CivilopediaDatabaseHelper(context).openConnection();
            cursor = conn.query(POLICY_TABLE, POLICY_COLS,
                    POLICY_COL_ID + "=?", new String [] { key },
                    null, null, null);
            cursor.moveToFirst();
            if (!cursor.isAfterLast()) {
                policy.setKey(cursor.getString(0));
                policy.setName(cursor.getString(1));
                policy.setCivilopedia(cursor.getString(2));
                policy.setHelp(cursor.getString(3));
                policy.setGroup(cursor.getString(4));
            }
        } catch (IOException e) {
            Log.e(TAG, "Failed to get policy (" + key + "): " + e.getLocalizedMessage());
        } finally {
            if (null != cursor) {
                cursor.close();
            }
            if (null != conn) {
                conn.close();
            }
        }
        return policy;
    }
} public class ReligionEntry extends CivilopediaEntry {
    private static final String TAG = ReligionEntry.class.getName();

    private static final String RELIGION_TABLE = "religion";
    private static final String RELIGION_COL_ID = ID;
    private static final String RELIGION_COL_NAME = NAME;
    private static final String RELIGION_COL_CIVILOPEDIA = "civilopedia";
    private static final String RELIGION_COL_TYPE = "type";
    private static final String RELIGION_COL_SORT_ORDER = "sort_order";
    private static final String [] RELIGION_COLS = new String [] {
        RELIGION_COL_ID,
        RELIGION_COL_NAME,
        RELIGION_COL_CIVILOPEDIA,
        RELIGION_COL_TYPE,
        RELIGION_COL_SORT_ORDER,
    };

    // Members
    private String mKey;
    private String mName;
    private String mGroup;
    private String mCivilopedia;

    public ReligionEntry() {
    }

    @Override
    public String getKey() {
        return mKey;
    }

    public void setKey(String key) {
        this.mKey = key;
    }

    @Override
    public String getName() {
        return mName;
    }

    public void setName(String name) {
        this.mName = name;
    }

    @Override
    public String getGroup() {
        return mGroup;
    }

    public void setGroup(String group) {
        mGroup = group;
    }

    public String getCivilopedia() {
        return mCivilopedia;
    }

    public void setCivilopedia(String civilopedia) {
        this.mCivilopedia = civilopedia;
    }

    public static ArrayList<String> getGroups(Context context) {
        ArrayList<String> result = new ArrayList<String>();
        SQLiteDatabase conn = null;
        Cursor cursor = null;
        try {
            // Gets distinct types sorted by cost
            conn = new CivilopediaDatabaseHelper(context).openConnection();
            cursor = conn.query(RELIGION_TABLE, new String [] { RELIGION_COL_TYPE }, null, null,
                    RELIGION_COL_TYPE, null, RELIGION_COL_SORT_ORDER + "," + RELIGION_COL_NAME);

            cursor.moveToFirst();
            while (!cursor.isAfterLast()) {
                result.add(cursor.getString(0));
                cursor.moveToNext();
            }
        } catch (IOException e) {
            Log.e(TAG, "Failed to get religion groups: " + e.getLocalizedMessage());
        } finally {
            if (null != cursor) {
                cursor.close();
            }
            if (null != conn) {
                conn.close();
            }
        }
        return result;
    }

    public static ArrayList<CivilopediaEntry> getEntries(Context context) {
        ArrayList<CivilopediaEntry> result = new ArrayList<CivilopediaEntry>();
        SQLiteDatabase conn = null;
        Cursor cursor = null;
        try {
            conn = new CivilopediaDatabaseHelper(context).openConnection();
            cursor = conn.query(RELIGION_TABLE, RELIGION_COLS, null, null,
                    null, null, RELIGION_COL_NAME + "," + RELIGION_COL_ID);

            cursor.moveToFirst();
            while (!cursor.isAfterLast()) {
                ReligionEntry belief = new ReligionEntry();
                belief.setKey(cursor.getString(0));
                belief.setName(cursor.getString(1));
                belief.setCivilopedia(cursor.getString(2));
                belief.setGroup(cursor.getString(3));
                result.add(belief);
                cursor.moveToNext();
            }
        } catch (IOException e) {
            Log.e(TAG, "Failed to get beliefs: " + e.getLocalizedMessage());
        } finally {
            if (null != cursor) {
                cursor.close();
            }
            if (null != conn) {
                conn.close();
            }
        }

        return result;
    }

    public static ReligionEntry getBeliefById(Context context, String key) {
        ReligionEntry belief = new ReligionEntry();
        SQLiteDatabase conn = null;
        Cursor cursor = null;

        try {
            conn = new CivilopediaDatabaseHelper(context).openConnection();
            cursor = conn.query(RELIGION_TABLE, RELIGION_COLS,
                    RELIGION_COL_ID + "=?", new String [] { key },
                    null, null, null);
            cursor.moveToFirst();
            if (!cursor.isAfterLast()) {
                belief.setKey(cursor.getString(0));
                belief.setName(cursor.getString(1));
                belief.setCivilopedia(cursor.getString(2));
                belief.setGroup(cursor.getString(3));
            }
        } catch (IOException e) {
            Log.e(TAG, "Failed to get belief (" + key + "): " + e.getLocalizedMessage());
        } finally {
            if (null != cursor) {
                cursor.close();
            }
            if (null != conn) {
                conn.close();
            }
        }
        return belief;
    }
} public class TechnologyEntry extends CivilopediaEntry {
    private static final String TAG = TechnologyEntry.class.getName();

    private static final String TECHNOLOGY_TABLE = "technology";
    private static final String TECHNOLOGY_COL_ID = ID;
    private static final String TECHNOLOGY_COL_NAME = NAME;
    private static final String TECHNOLOGY_COL_CIVILOPEDIA = "civilopedia";
    private static final String TECHNOLOGY_COL_HELP = "help";
    private static final String TECHNOLOGY_COL_QUOTE = "quote";
    private static final String TECHNOLOGY_COL_COST = "cost";
    private static final String TECHNOLOGY_COL_ERA = "era";
    private static final String [] TECHNOLOGY_COLS = new String [] {
        TECHNOLOGY_COL_ID,
        TECHNOLOGY_COL_NAME,
        TECHNOLOGY_COL_CIVILOPEDIA,
        TECHNOLOGY_COL_HELP,
        TECHNOLOGY_COL_QUOTE,
        TECHNOLOGY_COL_COST,
        TECHNOLOGY_COL_ERA,
    };

    // Members
    private String mKey;
    private String mName;
    private String mGroup;
    private String mQuote;
    private String mCivilopedia;
    private String mHelp;
    private int mCost;  // in "beakers"

    public TechnologyEntry() {
    }

    @Override
    public String getKey() {
        return mKey;
    }

    public void setKey(String key) {
        this.mKey = key;
    }

    @Override
    public String getName() {
        return mName;
    }

    public void setName(String name) {
        this.mName = name;
    }

    @Override
    public String getGroup() {
        return mGroup;
    }

    public void setGroup(String group) {
        mGroup = group;
    }

    public String getQuote() {
        return mQuote;
    }

    public void setQuote(String quote) {
        this.mQuote = quote;
    }

    public String getCivilopedia() {
        return mCivilopedia;
    }

    public void setCivilopedia(String civilopedia) {
        this.mCivilopedia = civilopedia;
    }

    public String getHelp() {
        return mHelp;
    }

    public void setHelp(String help) {
        this.mHelp = help;
    }

    public int getCost() {
        return mCost;
    }

    public void setCost(int cost) {
        this.mCost = cost;
    }

    public static ArrayList<String> getGroups(Context context) {
        ArrayList<String> result = new ArrayList<String>();
        SQLiteDatabase conn = null;
        Cursor cursor = null;
        try {
            // Gets distinct eras sorted by cost
            conn = new CivilopediaDatabaseHelper(context).openConnection();
            cursor = conn.query(TECHNOLOGY_TABLE, new String [] { TECHNOLOGY_COL_ERA }, null, null,
                    TECHNOLOGY_COL_ERA, null, TECHNOLOGY_COL_COST + "," + TECHNOLOGY_COL_ID);

            cursor.moveToFirst();
            while (!cursor.isAfterLast()) {
                result.add(cursor.getString(0));
                cursor.moveToNext();
            }
        } catch (IOException e) {
            Log.e(TAG, "Failed to get technology groups: " + e.getLocalizedMessage());
        } finally {
            if (null != cursor) {
                cursor.close();
            }
            if (null != conn) {
                conn.close();
            }
        }
        return result;
    }

    public static ArrayList<CivilopediaEntry> getEntries(Context context) {
        ArrayList<CivilopediaEntry> result = new ArrayList<CivilopediaEntry>();
        SQLiteDatabase conn = null;
        Cursor cursor = null;
        try {
            conn = new CivilopediaDatabaseHelper(context).openConnection();
            cursor = conn.query(TECHNOLOGY_TABLE, TECHNOLOGY_COLS, null, null,
                    null, null, TECHNOLOGY_COL_NAME + "," + TECHNOLOGY_COL_ID);

            cursor.moveToFirst();
            while (!cursor.isAfterLast()) {
                TechnologyEntry tech = new TechnologyEntry();
                tech.setKey(cursor.getString(0));
                tech.setName(cursor.getString(1));
                tech.setCivilopedia(cursor.getString(2));
                tech.setHelp(cursor.getString(3));
                tech.setQuote(cursor.getString(4));
                tech.setCost(cursor.getInt(5));
                tech.setGroup(cursor.getString(6));
                result.add(tech);
                cursor.moveToNext();
            }
        } catch (IOException e) {
            Log.e(TAG, "Failed to get technologies: " + e.getLocalizedMessage());
        } finally {
            if (null != cursor) {
                cursor.close();
            }
            if (null != conn) {
                conn.close();
            }
        }

        return result;
    }

    public static TechnologyEntry getTechnologyById(Context context, String key) {
        TechnologyEntry tech = new TechnologyEntry();
        SQLiteDatabase conn = null;
        Cursor cursor = null;

        try {
            conn = new CivilopediaDatabaseHelper(context).openConnection();
            cursor = conn.query(TECHNOLOGY_TABLE, TECHNOLOGY_COLS,
                    TECHNOLOGY_COL_ID + "=?", new String [] { key },
                    null, null, null);
            cursor.moveToFirst();
            if (!cursor.isAfterLast()) {
                tech.setKey(cursor.getString(0));
                tech.setName(cursor.getString(1));
                tech.setCivilopedia(cursor.getString(2));
                tech.setHelp(cursor.getString(3));
                tech.setQuote(cursor.getString(4));
                tech.setCost(cursor.getInt(5));
                tech.setGroup(cursor.getString(6));
            }
        } catch (IOException e) {
            Log.e(TAG, "Failed to get technology (" + key + "): " + e.getLocalizedMessage());
        } finally {
            if (null != cursor) {
                cursor.close();
            }
            if (null != conn) {
                conn.close();
            }
        }
        return tech;
    }
} public class UnitEntry extends CivilopediaEntry {
    private static final String TAG = UnitEntry.class.getName();

    private static final String UNIT_TABLE = "unit";
    private static final String UNIT_COL_ID = ID;
    private static final String UNIT_COL_NAME = NAME;
    private static final String UNIT_COL_CIVILOPEDIA = "civilopedia";
    private static final String UNIT_COL_HELP = "help";
    private static final String UNIT_COL_STRATEGY = "strategy";
    private static final String UNIT_COL_COST = "cost";
    private static final String UNIT_COL_FAITH_COST = "faith_cost";
    private static final String UNIT_COL_COMBAT = "combat";
    private static final String UNIT_COL_RANGED_COMBAT = "ranged_combat";
    private static final String UNIT_COL_MOVES = "moves";
    private static final String UNIT_COL_RANGE = "range";
    private static final String UNIT_COL_TECHNOLOGY = "technology";
    private static final String UNIT_COL_TYPE = "type";
    private static final String [] UNIT_COLS = new String [] {
        UNIT_COL_ID,
        UNIT_COL_NAME,
        UNIT_COL_CIVILOPEDIA,
        UNIT_COL_HELP,
        UNIT_COL_STRATEGY,
        UNIT_COL_COST,
        UNIT_COL_FAITH_COST,
        UNIT_COL_COMBAT,
        UNIT_COL_RANGED_COMBAT,
        UNIT_COL_MOVES,
        UNIT_COL_RANGE,
        UNIT_COL_TECHNOLOGY,
        UNIT_COL_TYPE,
    };

    // Members
    private String mKey;
    private String mName;
    private String mGroup;
    private String mStrategy;
    private String mCivilopedia;
    private String mHelp;
    private int mCost;  // in "hammers"
    private int mFaithCost;
    private int mCombat;
    private int mRangedCombat;
    private int mMoves;
    private int mRange;

    public UnitEntry() {
    }

    @Override
    public String getKey() {
        return mKey;
    }

    public void setKey(String key) {
        this.mKey = key;
    }

    @Override
    public String getName() {
        return mName;
    }

    public void setName(String name) {
        this.mName = name;
    }

    @Override
    public String getGroup() {
        return mGroup;
    }

    public void setGroup(String group) {
        mGroup = group;
    }

    public String getStrategy() {
        return mStrategy;
    }

    public void setStrategy(String strategy) {
        this.mStrategy = strategy;
    }

    public String getCivilopedia() {
        return mCivilopedia;
    }

    public void setCivilopedia(String civilopedia) {
        this.mCivilopedia = civilopedia;
    }

    public String getHelp() {
        return mHelp;
    }

    public void setHelp(String help) {
        this.mHelp = help;
    }

    public int getCost() {
        return mCost;
    }

    public void setCost(int cost) {
        this.mCost = cost;
    }

    public int getFaithCost() {
        return mFaithCost;
    }

    public void setFaithCost(int faithCost) {
        this.mFaithCost = faithCost;
    }

    public int getCombat() {
        return mCombat;
    }

    public void setCombat(int combat) {
        this.mCombat = combat;
    }

    public int getRangedCombat() {
        return mRangedCombat;
    }

    public void setRangedCombat(int rangedCombat) {
        this.mRangedCombat = rangedCombat;
    }

    public int getMoves() {
        return mMoves;
    }

    public void setMoves(int moves) {
        this.mMoves = moves;
    }

    public int getRange() {
        return mRange;
    }

    public void setRange(int range) {
        this.mRange = range;
    }

    public static ArrayList<String> getGroups(Context context) {
        ArrayList<String> result = new ArrayList<String>();
        SQLiteDatabase conn = null;
        Cursor cursor = null;
        try {
            conn = new CivilopediaDatabaseHelper(context).openConnection();
            cursor = conn.query(UNIT_TABLE, new String [] { UNIT_COL_TYPE }, null, null, UNIT_COL_TYPE,
                    null, UNIT_COL_COST + "," + UNIT_COL_ID);

            cursor.moveToFirst();
            while (!cursor.isAfterLast()) {
                result.add(cursor.getString(0));
                cursor.moveToNext();
            }
        } catch (IOException e) {
            Log.e(TAG, "Failed to get unit groups: " + e.getLocalizedMessage());
        } finally {
            if (null != cursor) {
                cursor.close();
            }
            if (null != conn) {
                conn.close();
            }
        }

        return result;
    }

    public static ArrayList<CivilopediaEntry> getEntries(Context context) {
        ArrayList<CivilopediaEntry> result = new ArrayList<CivilopediaEntry>();
        SQLiteDatabase conn = null;
        Cursor cursor = null;
        try {
            conn = new CivilopediaDatabaseHelper(context).openConnection();
            cursor = conn.query(UNIT_TABLE, UNIT_COLS, null, null, null,
                    null, UNIT_COL_NAME + "," + UNIT_COL_ID);

            cursor.moveToFirst();
            while (!cursor.isAfterLast()) {
                UnitEntry unit = new UnitEntry();
                unit.setKey(cursor.getString(0));
                unit.setName(cursor.getString(1));
                unit.setCivilopedia(cursor.getString(2));
                unit.setHelp(cursor.getString(3));
                unit.setStrategy(cursor.getString(4));
                unit.setCost(cursor.getInt(5));
                unit.setFaithCost(cursor.getInt(6));
                unit.setCombat(cursor.getInt(7));
                unit.setRangedCombat(cursor.getInt(8));
                unit.setMoves(cursor.getInt(9));
                unit.setRange(cursor.getInt(10));
                unit.setGroup(cursor.getString(12));
                result.add(unit);
                cursor.moveToNext();
            }
        } catch (IOException e) {
            Log.e(TAG, "Failed to get units: " + e.getLocalizedMessage());
        } finally {
            if (null != cursor) {
                cursor.close();
            }
            if (null != conn) {
                conn.close();
            }
        }

        return result;
    }

    public static UnitEntry getUnitById(Context context, String id) {
        UnitEntry unit = new UnitEntry();
        SQLiteDatabase conn = null;
        Cursor cursor = null;

        try {
            conn = new CivilopediaDatabaseHelper(context).openConnection();
            cursor = conn.query(UNIT_TABLE, UNIT_COLS,
                        UNIT_COL_ID + "=?", new String [] { id },
                        null, null, null);
            cursor.moveToFirst();
            if (!cursor.isAfterLast()) {
                unit.setKey(cursor.getString(0));
                unit.setName(cursor.getString(1));
                unit.setCivilopedia(cursor.getString(2));
                unit.setHelp(cursor.getString(3));
                unit.setStrategy(cursor.getString(4));
                unit.setCost(cursor.getInt(5));
                unit.setFaithCost(cursor.getInt(6));
                unit.setCombat(cursor.getInt(7));
                unit.setRangedCombat(cursor.getInt(8));
                unit.setMoves(cursor.getInt(9));
                unit.setRange(cursor.getInt(10));
                unit.setGroup(cursor.getString(12));
            }
        } catch (IOException e) {
            Log.e(TAG, "Failed to get unit (" + id + "): " + e.getLocalizedMessage());
        } finally {
            if (null != cursor) {
                cursor.close();
            }
            if (null != conn) {
                conn.close();
            }
        }

        return unit;
    }
} public class WonderEntry extends CivilopediaEntry {
    private static final String TAG = WonderEntry.class.getName();

    private static final String WONDER_TABLE = "wonder";
    private static final String WONDER_COL_ID = ID;
    private static final String WONDER_COL_NAME = NAME;
    private static final String WONDER_COL_CIVILOPEDIA = "civilopedia";
    private static final String WONDER_COL_HELP = "help";
    private static final String WONDER_COL_STRATEGY = "strategy";
    private static final String WONDER_COL_QUOTE = "quote";
    private static final String WONDER_COL_COST = "cost";
    private static final String WONDER_COL_TYPE = "type";
    private static final String [] WONDER_COLS = new String [] {
        WONDER_COL_ID,
        WONDER_COL_NAME,
        WONDER_COL_CIVILOPEDIA,
        WONDER_COL_HELP,
        WONDER_COL_STRATEGY,
        WONDER_COL_QUOTE,
        WONDER_COL_COST,
        WONDER_COL_TYPE,
    };

    // Members
    private String mKey;
    private String mName;
    private String mGroup;
    private String mStrategy;
    private String mQuote;
    private String mCivilopedia;
    private String mHelp;
    private int mCost;  // in "hammers"

    public WonderEntry() {
    }

    @Override
    public String getKey() {
        return mKey;
    }

    public void setKey(String key) {
        this.mKey = key;
    }

    @Override
    public String getName() {
        return mName;
    }

    public void setName(String name) {
        this.mName = name;
    }

    @Override
    public String getGroup() {
        return mGroup;
    }

    public void setGroup(String group) {
        mGroup = group;
    }

    public String getStrategy() {
        return mStrategy;
    }

    public void setStrategy(String strategy) {
        this.mStrategy = strategy;
    }

    public String getCivilopedia() {
        return mCivilopedia;
    }

    public void setCivilopedia(String civilopedia) {
        this.mCivilopedia = civilopedia;
    }

    public String getHelp() {
        return mHelp;
    }

    public void setHelp(String help) {
        this.mHelp = help;
    }

    public int getCost() {
        return mCost;
    }

    public void setCost(int cost) {
        this.mCost = cost;
    }

    public String getQuote() {
        return mQuote;
    }

    public void setQuote(String quote) {
        this.mQuote = quote;
    }

    public static ArrayList<String> getGroups(Context context) {
        ArrayList<String> result = new ArrayList<String>();
        SQLiteDatabase conn = null;
        Cursor cursor = null;
        try {
            conn = new CivilopediaDatabaseHelper(context).openConnection();
            cursor = conn.query(WONDER_TABLE, new String [] { WONDER_COL_TYPE }, null, null, WONDER_COL_TYPE,
                    null, WONDER_COL_COST + "," + WONDER_COL_ID);

            cursor.moveToFirst();
            while (!cursor.isAfterLast()) {
                result.add(cursor.getString(0));
                cursor.moveToNext();
            }
        } catch (IOException e) {
            Log.e(TAG, "Failed to get wonders: " + e.getLocalizedMessage());
        } finally {
            if (null != cursor) {
                cursor.close();
            }
            if (null != conn) {
                conn.close();
            }
        }

        return result;
    }

    public static ArrayList<CivilopediaEntry> getEntries(Context context) {
        ArrayList<CivilopediaEntry> result = new ArrayList<CivilopediaEntry>();
        SQLiteDatabase conn = null;
        Cursor cursor = null;
        try {
            conn = new CivilopediaDatabaseHelper(context).openConnection();
            cursor = conn.query(WONDER_TABLE, WONDER_COLS, null, null, null,
                    null, WONDER_COL_NAME + "," + WONDER_COL_ID);

            cursor.moveToFirst();
            while (!cursor.isAfterLast()) {
                WonderEntry wonder = new WonderEntry();
                wonder.setKey(cursor.getString(0));
                wonder.setName(cursor.getString(1));
                wonder.setGroup("Wonders");
                wonder.setCivilopedia(cursor.getString(2));
                wonder.setHelp(cursor.getString(3));
                wonder.setStrategy(cursor.getString(4));
                wonder.setQuote(cursor.getString(5));
                wonder.setCost(cursor.getInt(6));
                wonder.setGroup(cursor.getString(7));
                result.add(wonder);
                cursor.moveToNext();
            }
        } catch (IOException e) {
            Log.e(TAG, "Failed to get wonders: " + e.getLocalizedMessage());
        } finally {
            if (null != cursor) {
                cursor.close();
            }
            if (null != conn) {
                conn.close();
            }
        }

        return result;
    }

    public static WonderEntry getWonderById(Context context, String id) {
        WonderEntry wonder = new WonderEntry();
        SQLiteDatabase conn = null;
        Cursor cursor = null;

        try {
            conn = new CivilopediaDatabaseHelper(context).openConnection();
            cursor = conn.query(WONDER_TABLE, WONDER_COLS,
                    WONDER_COL_ID + "=?", new String [] { id },
                    null, null, null);
            cursor.moveToFirst();
            if (!cursor.isAfterLast()) {
                wonder.setKey(cursor.getString(0));
                wonder.setName(cursor.getString(1));
                wonder.setCivilopedia(cursor.getString(2));
                wonder.setHelp(cursor.getString(3));
                wonder.setStrategy(cursor.getString(4));
                wonder.setQuote(cursor.getString(5));
                wonder.setCost(cursor.getInt(6));
                wonder.setGroup(cursor.getString(7));
            }
        } catch (IOException e) {
            Log.e(TAG, "Failed to get wonder (" + id + "): " + e.getLocalizedMessage());
        } finally {
            if (null != cursor) {
                cursor.close();
            }
            if (null != conn) {
                conn.close();
            }
        }
        return wonder;
    }
} public class BuildingFragment extends CivilopediaFragment {
    private static final String TAG = BuildingFragment.class.getName();
    private static final String TYPE = "Buildings";
    private static final String BUILDING_TEMPLATE = "buildings.html";

    public BuildingFragment() {
        // Empty ctor for fragments required
    }

    @Override
    public String getType() {
        return TYPE;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        WebView layout = (WebView) inflater.inflate(R.layout.fragment_civilopedia_entry, container, false);

        String key = getArguments().getString(CivilopediaEntry.ID);
        String html = "";
        BuildingEntry building = BuildingEntry.getBuildingById(getActivity(), key);
        getActivity().setTitle(building.getName());

        AssetManager manager = getActivity().getAssets();
        try {
            InputStream stream = manager.open(BUILDING_TEMPLATE);
            byte [] buffer = new byte [stream.available()];
            stream.read(buffer);
            html = new String(buffer);
            stream.close();
        } catch (IOException e) {
            Log.e(TAG, "Failed to load building template: " + e.getLocalizedMessage());
        }

        HashMap<String, String> params = new HashMap<String, String>();
        params.put("key", building.getKey());
        params.put("name", building.getName());
        params.put("strategy", building.getStrategy());
        params.put("help", building.getHelp());
        params.put("civilopedia", building.getCivilopedia());
        params.put("cost", "" + building.getCost());
        params.put("faith_cost", "" + building.getFaithCost());
        params.put("maintenance", "" + building.getMaintenance());
        layout.loadDataWithBaseURL("file:///android_asset/", CivilopediaHtmlHelper.format(html, params), "text/html", "utf-8", "");

        return layout;
    }

    @Override
    public void onPause() {
        super.onPause();
    }
} public abstract class CivilopediaFragment extends Fragment {
    public abstract String getType();
} public class PolicyFragment extends CivilopediaFragment {
    private static final String TAG = PolicyFragment.class.getName();
    private static final String TYPE = "Social Policies";
    private static final String POLICY_TEMPLATE = "policies.html";

    public PolicyFragment() {
        // Empty ctor for fragments required
    }

    @Override
    public String getType() {
        return TYPE;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        WebView layout = (WebView) inflater.inflate(R.layout.fragment_civilopedia_entry, container, false);

        String key = getArguments().getString(CivilopediaEntry.ID);
        String html = "";

        PolicyEntry policy = PolicyEntry.getPolicyById(getActivity(), key);
        getActivity().setTitle(policy.getName());

        AssetManager manager = getActivity().getAssets();
        try {
            InputStream stream = manager.open(POLICY_TEMPLATE);
            byte [] buffer = new byte [stream.available()];
            stream.read(buffer);
            html = new String(buffer);
            stream.close();
        } catch (IOException e) {
            Log.e(TAG, "Failed to load policy template: " + e.getLocalizedMessage());
        }

        HashMap<String, String> params = new HashMap<String, String>();
        params.put("key", policy.getKey());
        params.put("name", policy.getName());
        params.put("help", policy.getHelp());
        params.put("civilopedia", policy.getCivilopedia());
        layout.loadDataWithBaseURL("file:///android_asset/", CivilopediaHtmlHelper.format(html, params), "text/html", "utf-8", "");

        return layout;
    }

    @Override
    public void onPause() {
        super.onPause();
    }
} public class ReligionFragment extends CivilopediaFragment {
    private static final String TAG = ReligionFragment.class.getName();
    private static final String TYPE = "Religion";
    private static final String RELIGION_TEMPLATE = "religion.html";

    public ReligionFragment() {
        // Empty ctor for fragments required
    }

    @Override
    public String getType() {
        return TYPE;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        WebView layout = (WebView) inflater.inflate(R.layout.fragment_civilopedia_entry, container, false);

        String key = getArguments().getString(CivilopediaEntry.ID);
        String html = "";

        ReligionEntry belief = ReligionEntry.getBeliefById(getActivity(), key);
        getActivity().setTitle(belief.getName());

        AssetManager manager = getActivity().getAssets();
        try {
            InputStream stream = manager.open(RELIGION_TEMPLATE);
            byte [] buffer = new byte [stream.available()];
            stream.read(buffer);
            html = new String(buffer);
            stream.close();
        } catch (IOException e) {
            Log.e(TAG, "Failed to load religion template: " + e.getLocalizedMessage());
        }

        HashMap<String, String> params = new HashMap<String, String>();
        params.put("key", belief.getKey());
        params.put("name", belief.getName());
        params.put("civilopedia", belief.getCivilopedia());
        layout.loadDataWithBaseURL("file:///android_asset/", CivilopediaHtmlHelper.format(html, params), "text/html", "utf-8", "");

        return layout;
    }

    @Override
    public void onPause() {
        super.onPause();
    }
} public class TechnologiesFragment extends CivilopediaFragment {
    private static final String TAG = TechnologiesFragment.class.getName();
    private static final String TYPE = "Technologies";
    private static final String TECHNOLOGY_TEMPLATE = "technologies.html";

    public TechnologiesFragment() {
        // Empty ctor for fragments required
    }

    @Override
    public String getType() {
        return TYPE;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        WebView layout = (WebView) inflater.inflate(R.layout.fragment_civilopedia_entry, container, false);

        String key = getArguments().getString(CivilopediaEntry.ID);
        String html = "";

        TechnologyEntry tech = TechnologyEntry.getTechnologyById(getActivity(), key);
        getActivity().setTitle(tech.getName());

        AssetManager manager = getActivity().getAssets();
        try {
            InputStream stream = manager.open(TECHNOLOGY_TEMPLATE);
            byte [] buffer = new byte [stream.available()];
            stream.read(buffer);
            html = new String(buffer);
            stream.close();
        } catch (IOException e) {
            Log.e(TAG, "Failed to load technology template: " + e.getLocalizedMessage());
        }

        HashMap<String, String> params = new HashMap<String, String>();
        params.put("key", tech.getKey());
        params.put("name", tech.getName());
        params.put("quote", tech.getQuote());
        params.put("help", tech.getHelp());
        params.put("civilopedia", tech.getCivilopedia());
        layout.loadDataWithBaseURL("file:///android_asset/", CivilopediaHtmlHelper.format(html, params), "text/html", "utf-8", "");

        return layout;
    }

    @Override
    public void onPause() {
        super.onPause();
    }
} public class UnitFragment extends CivilopediaFragment {
    private static final String TAG = UnitFragment.class.getName();
    private static final String TYPE = "Units";
    private static final String UNIT_TEMPLATE = "units.html";
    private CivilopediaDatabaseHelper mDatabase = null;

    public UnitFragment() {
        // Empty ctor for fragments required
    }

    @Override
    public String getType() {
        return TYPE;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        WebView layout = (WebView) inflater.inflate(R.layout.fragment_civilopedia_entry, container, false);

        String id = getArguments().getString(CivilopediaEntry.ID);
        String html = "";
        UnitEntry unit = UnitEntry.getUnitById(getActivity(), id);
        getActivity().setTitle(unit.getName());

        AssetManager manager = getActivity().getAssets();
        try {
            InputStream stream = manager.open(UNIT_TEMPLATE);
            byte [] buffer = new byte [stream.available()];
            stream.read(buffer);
            html = new String(buffer);
            stream.close();
        } catch (IOException e) {
            Log.e(TAG, "Failed to load unit template: " + e.getLocalizedMessage());
        }

        HashMap<String, String> params = new HashMap<String, String>();
        params.put("key", unit.getKey());
        params.put("name", unit.getName());
        params.put("strategy", unit.getStrategy());
        params.put("help", unit.getHelp());
        params.put("civilopedia", unit.getCivilopedia());
        layout.loadDataWithBaseURL("file:///android_asset/", CivilopediaHtmlHelper.format(html, params), "text/html", "utf-8", "");

        return layout;
    }

    @Override
    public void onPause() {
        super.onPause();
        if (null != mDatabase) {
            mDatabase.close();
        }
    }
} public class WonderFragment extends CivilopediaFragment {
    private static final String TAG = WonderFragment.class.getName();
    private static final String TYPE = "Wonders";
    private static final String WONDER_TEMPLATE = "wonders.html";

    public WonderFragment() {
        // Empty ctor for fragments required
    }

    @Override
    public String getType() {
        return TYPE;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        WebView layout = (WebView) inflater.inflate(R.layout.fragment_civilopedia_entry, container, false);

        String id = getArguments().getString(CivilopediaEntry.ID);
        String html = "";
        WonderEntry wonder = WonderEntry.getWonderById(getActivity(), id);
        getActivity().setTitle(wonder.getName());

        AssetManager manager = getActivity().getAssets();
        try {
            InputStream stream = manager.open(WONDER_TEMPLATE);
            byte [] buffer = new byte [stream.available()];
            stream.read(buffer);
            html = new String(buffer);
            stream.close();
        } catch (IOException e) {
            Log.e(TAG, "Failed to load wonder template: " + e.getLocalizedMessage());
        }

        HashMap<String, String> params = new HashMap<String, String>();
        params.put("key", wonder.getKey());
        params.put("name", wonder.getName());
        params.put("strategy", wonder.getStrategy());
        params.put("help", wonder.getHelp());
        params.put("civilopedia", wonder.getCivilopedia());
        params.put("quote", wonder.getQuote());
        params.put("cost", "" + wonder.getCost());
        layout.loadDataWithBaseURL("file:///android_asset/", CivilopediaHtmlHelper.format(html, params), "text/html", "utf-8", "");

        return layout;
    }

    @Override
    public void onPause() {
        super.onPause();
    }
} public class SubheadingExpandableListAdapter extends BaseExpandableListAdapter {

    public static final String ITEM_KEY = "_id";

    private static final String TAG = SubheadingExpandableListAdapter.class.getName();

    private static final int NUM_CHILD_TYPES = 2;
    private static final int SUBHEADING_TYPE = 0;
    private static final int SUBLISTING_TYPE = 1;

    private List<? extends Map<String, ?>> mGroupData;
    private int mGroupLayout;
    private String[] mGroupFrom;
    private int[] mGroupTo;
    private List<? extends List<? extends Map<String, ?>>> mSubheadings;
    private int mSubheadingLayout;
    private String[] mSubheadingFrom;
    private int[] mSubheadingTo;
    private List<? extends List<? extends List<? extends Map<String, ?>>>> mSublistingData;
    private int mSublistingLayout;
    private String[] mSublistingFrom;
    private int[] mSublistingTo;

    private LayoutInflater mInflater;

    public SubheadingExpandableListAdapter(Context context,
            List<? extends Map<String, ?>> groupData, int groupLayout,
            String[] groupFrom, int[] groupTo,
            List<? extends List<? extends Map<String, ?>>> subheadings, int subheadingLayout,
            String[] subheadingFrom, int[] subheadingTo,
            List<? extends List<? extends List<? extends Map<String, ?>>>> sublistingData,
            int sublistingLayout, String[] sublistingFrom, int[] sublistingTo) {
        mGroupData = groupData;
        mGroupLayout = groupLayout;
        mGroupFrom = groupFrom;
        mGroupTo = groupTo;
        mSubheadings = subheadings;
        mSubheadingLayout = subheadingLayout;
        mSubheadingFrom = subheadingFrom;
        mSubheadingTo = subheadingTo;
        mSublistingData = sublistingData;
        mSublistingLayout = sublistingLayout;
        mSublistingFrom = sublistingFrom;
        mSublistingTo = sublistingTo;

        mInflater = (LayoutInflater) context
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        // Verify that the passed in data is the correct sizes
        if (groupData.size() != subheadings.size() || groupData.size() != sublistingData.size()) {
            Log.w(TAG, String.format("Incorrect sizes of lists. len(groups) = %d, len(subheadings) = %d, len(sublistings) = %d", groupData.size(), subheadings.size(), sublistingData.size()));
        } else {
            for (int i = 0; i < groupData.size(); i += 1) {
                if (subheadings.get(i).size() != sublistingData.get(i).size()) {
                    Log.w(TAG, String.format("Incorrect size of sublist for group (%d). len(subheadings.get(%d)) = %d, len(sublistings.get(%d)) = %d", i, i, subheadings.get(i).size(), i, sublistingData.get(i).size()));
                }
            }
        }
    }

    @Override
    public Object getChild(int groupPosition, int childPosition) {
        int index = 0, sublistingCount;
        for (int i = 0; i < mSubheadings.get(groupPosition).size(); i += 1) {

            if (index == childPosition) {
                return mSubheadings.get(groupPosition).get(i);
            }
            index += 1;
            sublistingCount = mSublistingData.get(groupPosition).get(i).size();
            if (childPosition - index < sublistingCount) {
                return mSublistingData.get(groupPosition).get(i).get(childPosition - index);
            } else {
                index += sublistingCount;
            }
        }

        Log.w(TAG, String.format("getChild returned null for (%d, %d)", groupPosition, childPosition));
        return null;
    }

    @Override
    public long getChildId(int groupPosition, int childPosition) {
        return childPosition;
    }

    /**
     * Returns either a sublisting view or a subheading view.
     */
    @SuppressWarnings("unchecked")
    @Override
    public View getChildView(int groupPosition, int childPosition,
            boolean isLastChild, View convertView, ViewGroup parent) {
        View v;
        boolean isSublisting = getChildType(groupPosition, childPosition) == SUBLISTING_TYPE;

        if (null == convertView) {
            if (isSublisting) {
                v = mInflater.inflate(mSublistingLayout, parent, false);
            } else {
                v = mInflater.inflate(mSubheadingLayout, parent, false);
            }
        } else {
            v = convertView;
        }

        if (isSublisting) {
            bindView(v,
                    (Map<String, ?>) getChild(groupPosition, childPosition),
                    mSublistingFrom, mSublistingTo);
        } else {
            bindView(v, (Map<String, ?>) getChild(groupPosition, childPosition),
                    mSubheadingFrom, mSubheadingTo);
        }

        return v;
    }

    @Override
    public int getChildrenCount(int groupPosition) {
        int count = 0;

        for (List<? extends Map<String, ?>> sublistings : mSublistingData.get(groupPosition)) {
            count += 1; // add one for the section
            count += sublistings.size();
        }
        return count;
    }

    @Override
    public Object getGroup(int groupPosition) {
        return mGroupData.get(groupPosition);
    }

    @Override
    public int getGroupCount() {
        return mGroupData.size();
    }

    @Override
    public long getGroupId(int groupPosition) {
        return groupPosition;
    }

    @Override
    public View getGroupView(int groupPosition, boolean isExpanded,
            View convertView, ViewGroup parent) {
        View v;

        if (null == convertView) {
            v = mInflater.inflate(mGroupLayout, parent, false);
        } else {
            v = convertView;
        }
        bindView(v, mGroupData.get(groupPosition), mGroupFrom, mGroupTo);
        return v;
    }

    @Override
    public boolean hasStableIds() {
        return true;
    }

    /**
     * Sublistings are selectable. Subheadings are not.
     */
    @Override
    public boolean isChildSelectable(int groupPosition, int childPosition) {
        if (getChildType(groupPosition, childPosition) == SUBLISTING_TYPE) {
            return true;
        }
        return false;
    }

    /**
     * Returns a either the sublisting type or the subheading type.
     */
    @Override
    public int getChildType(int groupPosition, int childPosition) {
        int index = 0, sublistingCount;
        for (int i = 0; i < mSubheadings.get(groupPosition).size(); i += 1) {

            if (index == childPosition) {
                return SUBHEADING_TYPE;
            }
            index += 1;
            sublistingCount = mSublistingData.get(groupPosition).get(i).size();
            if (childPosition - index < sublistingCount) {
                return SUBLISTING_TYPE;
            } else {
                index += sublistingCount;
            }
        }
        Log.w(TAG, String.format("Default child type returned for (%d, %d)", groupPosition, childPosition));
        return SUBHEADING_TYPE;
    }

    @Override
    public int getChildTypeCount() {
        return NUM_CHILD_TYPES;
    }

    @SuppressWarnings("unchecked")
    public String getChildKey(int groupPosition, int childPosition) {
        if (getChildType(groupPosition, childPosition) != SUBLISTING_TYPE) {
            Log.w(TAG, "Subheadings do not have keys!");
            return null;
        }
        return (String) ((Map<String, ?>) getChild(groupPosition, childPosition)).get(ITEM_KEY);
    }

    private void bindView(View view, Map<String, ?> data, String[] from, int[] to) {
        if (from.length != to.length) {
            Log.w(TAG, String.format("Bind view len(from) = %d, len(to) = %d", from.length, to.length));
        }
        for (int i = 0; i < to.length && i < from.length; i += 1) {
            TextView v = (TextView) view.findViewById(to[i]);
            if (null != v) {
                v.setText((String) data.get(from[i]));
            }
        }
    }
}
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import name.davidfischer.civilopedia.entries.BuildingEntry;
import name.davidfischer.civilopedia.entries.CivilopediaEntry;
import name.davidfischer.civilopedia.entries.PolicyEntry;
import name.davidfischer.civilopedia.entries.ReligionEntry;
import name.davidfischer.civilopedia.entries.TechnologyEntry;
import name.davidfischer.civilopedia.entries.UnitEntry;
import name.davidfischer.civilopedia.entries.WonderEntry;
import name.davidfischer.civilopedia.fragments.BuildingFragment;
import name.davidfischer.civilopedia.fragments.CivilopediaFragment;
import name.davidfischer.civilopedia.fragments.PolicyFragment;
import name.davidfischer.civilopedia.fragments.ReligionFragment;
import name.davidfischer.civilopedia.fragments.TechnologiesFragment;
import name.davidfischer.civilopedia.fragments.UnitFragment;
import name.davidfischer.civilopedia.fragments.WonderFragment;
import name.davidfischer.civilopedia.ui.SubheadingExpandableListAdapter;
import android.app.Activity;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.v4.app.ActionBarDrawerToggle;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ExpandableListView;
package name.davidfischer.civilopedia;



public class CivilopediaActivity extends Activity {
    private static final String TAG = CivilopediaActivity.class.getName();
    public static final String CATEGORY = "CATEGORY";
    public static final String CATEGORY_SUBHEADING = "CATEGORY_SUBHEADING";

    // Must match strings.xml
    private static final String TECHNOLOGIES = "TECHNOLOGIES";
    private static final String UNITS = "UNITS";
    private static final String BUILDINGS = "BUILDINGS";
    private static final String WONDERS = "WONDERS";
    private static final String RELIGION = "RELIGION";
    private static final String POLICIES = "SOCIAL POLICIES";

    private DrawerLayout mDrawerLayout;
    private ExpandableListView mDrawerList;
    private ActionBarDrawerToggle mDrawerToggle;
    private CharSequence mTitle;
    private String [] mCategoryNames;
    private List<? extends Map<String, ?>> mCategoryList;
    private List<? extends List<? extends List<? extends Map<String, ?>>>> mSubcategoryList;
    private List<? extends List<? extends Map<String, ?>>> mSubheadingList;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mCategoryNames = getResources().getStringArray(R.array.civ_categories);
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerList = (ExpandableListView) findViewById(R.id.left_drawer);
        mCategoryList = createCategoryList();
        mSubcategoryList = createSubcategoryList();
        mSubheadingList = createSubheadingList();

        // Set the adapter for the expandable list view
        // This sets the various category texts into the left panel
        SubheadingExpandableListAdapter expListAdapter =