src/main/java/org/centum/android/model/StackManager.java
public class StackManager {

    public static final int EVENT_STACK_ADDED = 0;
    public static final int EVENT_STACK_REMOVED = 1;
    public static final int EVENT_STACK_MOVED = 2;
    public static final int EVENT_ARCHIVED_STACK_ADDED = 3;
    public static final int EVENT_ARCHIVED_STACK_REMOVED = 4;
    public static final int EVENT_STACK_ARCHIVE_STATUS_CHANGED = 5;
    private static final String DEBUG_TAG = "StackManager";
    private static StackManager instance = null;
    private final List<StackManagerListener> listeners = new ArrayList<StackManagerListener>();
    private final List<Stack> stacks = new ArrayList<Stack>();
    private final List<Stack> archivedStacks = new ArrayList<Stack>();

    private StackManager() {
    }

    public static StackManager get() {
        instance = instance == null ? new StackManager() : instance;
        return instance;
    }

    public static String getValidName(String name) {
        if (!StackManager.get().containsStack(name)) {
            return name;
        }
        int i = 1;
        while (StackManager.get().containsStack(name + " (" + i + ")")) {
            i++;
        }
        return name + " (" + i + ")";
    }

    public void setData(LoadedData loadedData) {
        List<Stack> archivedStacks = loadedData.getLoadedArchivedStacks();
        List<Stack> stacks = loadedData.getLoadedStacks();

        removeAllArchivedStacks();
        removeAllStacks();

        for (Stack stack : stacks) {
            StackManager.get().addStack(stack);
        }
        for (Stack stack : archivedStacks) {
            StackManager.get().addArchiveStack(stack);
        }
    }

    public void addStack(Stack stack, int pos) {
        if (archivedStacks.contains(stack)) {
            restoreArchivedStack(stack);
            return;
        } else if (containsStack(stack)) {
            stack.setName(getValidName(stack.getName()));
        }
        stacks.add(Math.max(0, Math.min(stacks.size(), pos)), stack);
        fireEvent(stack, EVENT_STACK_ADDED);
    }

    public boolean removeStack(Stack stack, boolean archive) {
        if (stack != null) {
            stacks.remove(stack);
            if (!archive) {
                fireEvent(stack, EVENT_STACK_REMOVED);
            } else {
                archivedStacks.add(stack);
                fireEvent(stack, EVENT_STACK_ARCHIVE_STATUS_CHANGED);
            }
            return true;
        }
        return false;
    }

    public void moveStack(Stack stack, int pos) {
        if (stack != null) {
            stacks.remove(stack);
            stacks.add(pos, stack);
            fireEvent(stack, EVENT_STACK_MOVED);
        }
    }

    public void undoRemoveStack() {
        if (archivedStacks.size() > 0) {
            restoreArchivedStack(archivedStacks.size() - 1);
        }
    }

    public int getStackPosition(Stack stack) {
        for (int i = 0; i < getNumberOfStacks(); i++) {
            if (stacks.get(i) == stack) {
                return i;
            }
        }
        return -1;
    }

    public int getNumberOfStacks() {
        return stacks.size();
    }

    public Stack getStack(String name) {
        for (Stack stack : stacks) {
            if (stack.getName().equals(name)) {
                return stack;
            }
        }
        return null;
    }

    public void addStack(Stack stack) {
        addStack(stack, stacks.size());
    }

    public boolean removeStack(Stack stack) {
        return removeStack(stack, true);
    }

    public Stack getStack(int i) {
        return stacks.get(i);
    }

    public boolean containsStack(Stack stack) {
        return containsStack(stack.getName());
    }

    public boolean containsStack(String input) {
        return (getStack(input) != null || getArchivedStack(input) != null);
    }

    public String[] getStackNames() {
        String[] names = new String[getNumberOfStacks()];
        for (int i = 0; i < getNumberOfStacks(); i++) {
            names[i] = stacks.get(i).getName();
        }
        return names;
    }

    public List<Stack> getStackList() {
        return stacks;
    }

    public void removeAllStacks() {
        while (stacks.size() > 0) {
            stacks.remove(0);
        }
    }

    /**
     * ****************************ARCHIVED STACKS******************************
     */

    public void addArchiveStack(Stack stack) {
        if (containsArchivedStack(stack)) {
            stack.setName(getValidName(stack.getName()));
        }
        archivedStacks.add(stack);
        fireEvent(stack, EVENT_ARCHIVED_STACK_ADDED);
    }

    public void removeArchivedStack(Stack stack) {
        archivedStacks.remove(stack);
        fireEvent(stack, EVENT_ARCHIVED_STACK_REMOVED);
    }

    public Stack getArchivedStack(String name) {
        for (Stack stack : archivedStacks) {
            if (stack.getName().equals(name)) {
                return stack;
            }
        }
        return null;
    }

    public Stack getArchivedStack(int i) {
        return archivedStacks.get(i);
    }

    public void removeArchivedStack(int stack) {
        removeArchivedStack(archivedStacks.get(stack));
    }

    public void removeAllArchivedStacks() {
        while (archivedStacks.size() > 0) {
            removeArchivedStack(0);
        }
    }

    public void restoreArchivedStack(Stack stack) {
        archivedStacks.remove(stack);
        String name = stack.getName();
        if (containsStack(name)) {
            int suffix = 1;
            while (getStack(name + " (" + suffix + ")") != null) {
                suffix++;
            }
            stack.setName(name + " (" + suffix + ")");
        }
        stacks.add(stack);
        fireEvent(stack, EVENT_STACK_ARCHIVE_STATUS_CHANGED);
    }

    public void restoreArchivedStack(int i) {
        restoreArchivedStack(archivedStacks.get(i));
    }

    public void restoreAllArchivedStacks() {
        while (getNumberOfArchivedStacks() > 0) {
            restoreArchivedStack(archivedStacks.get(0));
        }
    }

    public boolean containsArchivedStack(Stack stack) {
        return containsArchivedStack(stack.getName());
    }

    public boolean containsArchivedStack(String name) {
        return getArchivedStack(name) != null;
    }

    public List<Stack> getArchivedStacksList() {
        return archivedStacks;
    }

    public int getNumberOfArchivedStacks() {
        return archivedStacks.size();
    }

    public void setCurrentStack(Stack stack) {
        for (Stack s : stacks) {
            if (s != stack)
                s.setCurrentStack(false);
        }
        for (Stack s : archivedStacks) {
            if (s != stack)
                s.setCurrentStack(false);
        }
        if (stack != null) {
            stack.setCurrentStack(true);
        }
    }

    /**
     * ****************************EVENTS******************************
     */

    public void addListener(StackManagerListener listener) {
        if (!listeners.contains(listener))
            listeners.add(listener);
    }

    public void removeListener(StackManagerListener listener) {
        listeners.remove(listener);
    }

    private void fireEvent(StackManagerEvent evt) {
        for (StackManagerListener listener : listeners) {
            listener.eventFired(evt);
        }
    }

    private void fireEvent(Stack stack, int evt) {
        fireEvent(new StackManagerEvent(stack, evt));
    }

    public List<Stack> getArchivedStackList() {
        return archivedStacks;
    }
}
src/main/java/org/centum/android/model/events/StackManagerEvent.java
public final class StackManagerEvent {

    private final Stack target;
    private final int event;

    public StackManagerEvent(Stack target, int event) {
        this.target = target;
        this.event = event;
    }

    public Stack getTarget() {
        return target;
    }

    public int getEvent() {
        return event;
    }
}
src/main/java/org/centum/android/model/events/PlaySessionListener.java
public interface PlaySessionListener {

    public void eventFired(PlaySessionEvent evt);

}
src/main/java/org/centum/android/model/events/StackEvent.java
public class StackEvent {

    private final Stack source;
    private final Card target;
    private final PlaySession psTarget;
    private final int event;

    public StackEvent(Stack source, Card target, PlaySession psTarget, int event) {
        this.source = source;
        this.target = target;
        this.psTarget = psTarget;
        this.event = event;
    }

    public PlaySession getPsTarget() {
        return psTarget;
    }

    public Stack getSource() {
        return source;
    }

    public Card getTarget() {
        return target;
    }

    public int getEvent() {
        return event;
    }

}
src/main/java/org/centum/android/model/events/CardListener.java
public interface CardListener {
    public void eventFired(CardEvent evt);
}
src/main/java/org/centum/android/model/Stack.java
public class Stack implements Externalizable {

    public static final int EVENT_CARD_ADDED = 0;
    public static final int EVENT_CARD_REMOVED = 1;
    public static final int EVENT_ARCHIVED_CARD_ADDED = 9;
    public static final int EVENT_ARCHIVED_CARD_REMOVED = 10;
    public static final int EVENT_CARD_MOVED = 2;
    public static final int EVENT_NAME_CHANGED = 3;
    public static final int EVENT_DESCRIPTION_CHANGED = 4;
    public static final int EVENT_ICON_CHANGED = 5;
    public static final int EVENT_SELECTION_CHANGED = 6;
    public static final int EVENT_PLAY_SESSION_ADDED = 7;
    public static final int EVENT_PLAY_SESSION_REMOVED = 8;
    public static final int EVENT_CARD_ARCHIVE_STATUS_CHANGED = 11;
    public static final int EVENT_CURRENT = 12;
    public static final String[] ICONS = new String[]{"circle_stack", "circle_bio",
            "circle_book", "circle_chem", "circle_gavel",
            "circle_music", "circle_pencil", "circle_plus"};
    private static final long serialVersionUID = -6819255155280303381L;
    private static final int externalizableVersion = 3;
    private static final String DEBUG_TAG = "Stack";
    private final ArrayList<Card> cards = new ArrayList<Card>();
    private final ArrayList<PlaySession> playSessions = new ArrayList<PlaySession>();
    private final ArrayList<StackListener> listeners = new ArrayList<StackListener>();
    private ArrayList<Card> archivedCards = new ArrayList<Card>();
    private String name = null;
    private String description = "";
    private int icon = 0;
    private boolean selected;
    private boolean currentStack = false;
    private boolean isQuizletStack = false;
    private int quizletID = -1;
    private long sqlID = -1;

    public Stack() {

    }

    public Stack(String name) {
        this.name = name;
    }

    public void addPlaySession(PlaySession playSession) {
        int i = 0;
        String oName = playSession.getName();
        String name = oName;
        while (containsPlaySession(name)) {
            i++;
            name = oName + " (" + i + ")";
        }
        playSession.setName(name);
        playSessions.add(playSession);
        fireEvent(this, playSession, EVENT_PLAY_SESSION_ADDED, 0);
    }

    public void removePlaySession(PlaySession playSession) {
        playSessions.remove(playSession);
        fireEvent(this, playSession, EVENT_PLAY_SESSION_REMOVED, 0);
    }

    public boolean containsPlaySession(String name) {
        for (PlaySession playSession : playSessions) {
            if (name.equals(playSession.getName())) {
                return true;
            }
        }
        return false;
    }

    public boolean containsPlaySession(PlaySession playSession) {
        for (PlaySession ps : playSessions) {
            if (playSession == ps) {
                return true;
            }
        }
        return false;
    }

    public int getNumberOfPlaySessions() {
        return playSessions.size();
    }

    public PlaySession getPlaySession(String id) {
        for (PlaySession session : playSessions) {
            if (session.getId().equals(id)) {
                return session;
            }
        }
        return null;
    }

    public PlaySession getPlaySession(int i) {
        return playSessions.get(i);
    }

    public PlaySession[] getPlaySessions() {
        return playSessions.toArray(new PlaySession[playSessions.size()]);
    }

    public PlayStats getPlayStatsWithEnabledSessions() {
        PlayStats playStats = new PlayStats();
        for (PlaySession playSession : playSessions) {
            if (playSession.isEnabled()) {
                playStats.addPlaySession(playSession);
            }
        }
        return playStats;
    }

    public int getNumberOfEnabledSessions() {
        int i = 0;
        for (PlaySession playSession : playSessions) {
            if (playSession.isEnabled()) {
                i++;
            }
        }
        return i;
    }

    public boolean addCard(Card card) {
        return addCard(card, cards.size());
    }

    public void quickAddCard(Card card) {
        cards.add(card);
    }

    public boolean addCard(Card card, int pos) {
        if (!containsCard(card)) {
            cards.add(pos, card);
            fireEvent(this, card, EVENT_CARD_ADDED);
            return true;
        }
        return false;
    }

    public boolean removeCard(Card card) {
        return removeCard(card, true);
    }

    public boolean removeCard(Card card, boolean archive) {
        if (containsCard(card)) {
            cards.remove(card);
            if (!archive) {
                fireEvent(this, card, EVENT_CARD_REMOVED);
            } else {
                archivedCards.add(card);
                fireEvent(this, card, EVENT_CARD_ARCHIVE_STATUS_CHANGED);
            }
            return true;
        }
        return false;
    }

    public int getCardPosition(Card card) {
        return cards.indexOf(card);
    }

    public Card getCard(int index) {
        if (index >= 0 && index < getNumberOfCards()) {
            return cards.get(index);
        }
        return null;
    }

    public boolean containsCard(Card c) {
        return cards.contains(c);
    }

    public int getNumberOfCards() {
        return cards.size();
    }

    public int getNumberOfArchivedCards() {
        if (archivedCards == null) {
            archivedCards = new ArrayList<Card>();
        }
        return archivedCards.size();
    }

    public ArrayList<Card> getArchivedCards() {
        return archivedCards;
    }

    public Card getArchivedCard(int i) {
        return archivedCards.get(i);
    }

    public void restoreArchivedCard(int i) {
        if (archivedCards.size() > i) {
            restoreArchivedCard(archivedCards.get(i));
        }
    }

    public void restoreArchivedCard(Card card) {
        archivedCards.remove(card);
        cards.add(card);
        fireEvent(this, card, EVENT_CARD_ARCHIVE_STATUS_CHANGED);
    }

    public void removeArchivedCard(int i) {
        if (i < archivedCards.size()) {
            removeArchivedCard(archivedCards.get(i));
        }
    }

    public void removeArchivedCard(Card card) {
        archivedCards.remove(card);
        fireEvent(this, card, EVENT_ARCHIVED_CARD_REMOVED);
    }

    public void removeAllArchivedCards() {
        while (archivedCards.size() > 0) {
            removeArchivedCard(0);
        }
    }

    public void restoreAllArchivedCards() {
        while (getNumberOfArchivedCards() > 0) {
            restoreArchivedCard(0);
        }
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        fireEvent(this, null, EVENT_NAME_CHANGED);
    }

    public int getIcon() {
        return icon;
    }

    public void setIcon(int icon) {
        this.icon = icon;
        fireEvent(this, null, EVENT_ICON_CHANGED);
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
        if (this.description.length() > 500) {
            this.description = this.description.substring(0, 500);
        }
        fireEvent(this, null, EVENT_DESCRIPTION_CHANGED);
    }

    public void moveCard(Card card, int pos) {
        cards.remove(card);
        cards.add(pos, card);
        fireEvent(this, null, EVENT_CARD_MOVED);
    }

    public void undoRemoveCard() {
        if (archivedCards.size() > 0) {
            restoreArchivedCard(archivedCards.size() - 1);
        }
    }

    public boolean isSelected() {
        return selected;
    }

    public void setSelected(boolean selected) {
        this.selected = selected;
        fireEvent(this, null, EVENT_SELECTION_CHANGED);
    }

    public String toString() {
        return getName();
    }

    public List<Card> getCardList() {
        return cards;
    }

    public void shuffle() {
        Collections.shuffle(cards);
        fireEvent(this, null, EVENT_CARD_MOVED);
    }

    public void sortByWrong() {
        final PlayStats playStats = getPlayStatsWithEnabledSessions();
        Collections.sort(cards, new Comparator<Card>() {
            @Override
            public int compare(Card card, Card card2) {
                return Double.compare(playStats.getPercentWrong(card2), playStats.getPercentWrong(card));
            }
        });
        fireEvent(this, null, EVENT_CARD_MOVED);
    }

    public void sortByCorrect() {
        final PlayStats playStats = getPlayStatsWithEnabledSessions();
        Collections.sort(cards, new Comparator<Card>() {
            @Override
            public int compare(Card card, Card card2) {
                return Double.compare(playStats.getPercentCorrect(card2), playStats.getPercentCorrect(card));
            }
        });
        fireEvent(this, null, EVENT_CARD_MOVED);
    }

    public boolean isQuizletStack() {
        return isQuizletStack;
    }

    public void setQuizletStack(boolean isQuizletStack) {
        this.isQuizletStack = isQuizletStack;
    }

    public int getQuizletID() {
        return quizletID;
    }

    public void setQuizletID(int quizletID) {
        this.quizletID = quizletID;
    }

    private void disableQuizletSet() {
        isQuizletStack = false;
        quizletID = -1;
    }

    @Override
    public void readExternal(ObjectInput objectInput) throws IOException, ClassNotFoundException {
        int version = objectInput.readInt();

        name = objectInput.readUTF();
        description = objectInput.readUTF();
        icon = objectInput.readInt();
        int cards = objectInput.readInt();
        int archived = objectInput.readInt();
        int sessions = objectInput.readInt();

        for (int i = 0; i < cards; i++) {
            this.cards.add((Card) objectInput.readObject());
        }
        for (int i = 0; i < archived; i++) {
            this.archivedCards.add((Card) objectInput.readObject());
        }
        for (int i = 0; i < sessions; i++) {
            this.playSessions.add((PlaySession) objectInput.readObject());
        }

        if (version > 1) {
            isQuizletStack = objectInput.readBoolean();
        }
        if (version > 2) {
            quizletID = objectInput.readInt();
        }
    }

    @Override
    public boolean equals(Object stack) {
        return (stack instanceof Stack && ((Stack) stack).getName().equals(getName()));
    }

    @Override
    public void writeExternal(ObjectOutput objectOutput) throws IOException {
        objectOutput.writeInt(externalizableVersion);
        objectOutput.writeUTF(name);
        objectOutput.writeUTF(description);
        objectOutput.writeInt(icon);
        objectOutput.writeInt(cards.size());
        objectOutput.writeInt(archivedCards.size());
        objectOutput.writeInt(playSessions.size());

        for (int i = 0; i < cards.size(); i++) {
            objectOutput.writeObject(cards.get(i));
        }
        for (int i = 0; i < archivedCards.size(); i++) {
            objectOutput.writeObject(archivedCards.get(i));
        }
        for (int i = 0; i < playSessions.size(); i++) {
            objectOutput.writeObject(playSessions.get(i));
        }

        objectOutput.writeBoolean(isQuizletStack);
        objectOutput.writeInt(quizletID);
    }

    public void sortByName() {
        Collections.sort(cards, new Comparator<Card>() {
            @Override
            public int compare(Card card, Card card2) {
                return card.getTitle().toLowerCase().compareTo(card2.getTitle().toLowerCase());
            }
        });
        fireEvent(this, null, EVENT_CARD_MOVED);
    }

    public long getSqlID() {
        return sqlID;
    }

    public void setSqlID(long sqlID) {
        this.sqlID = sqlID;
    }

    public void addArchivedCard(Card card) {
        archivedCards.add(card);
        fireEvent(this, card, EVENT_ARCHIVED_CARD_ADDED);
    }

    public void quickAddArchivedCard(Card card) {
        archivedCards.add(card);
    }

    public boolean isCurrentStack() {
        return currentStack;
    }

    public void setCurrentStack(boolean currentStack) {
        this.currentStack = currentStack;
        fireEvent(this, null, EVENT_CURRENT);
    }

    private void fireEvent(StackEvent event) {
        for (StackListener listener : listeners) {
            listener.eventFired(event);
        }
    }

    private void fireEvent(Stack stack, Card card, int evt) {
        fireEvent(new StackEvent(stack, card, null, evt));
    }

    private void fireEvent(Stack stack, PlaySession ps, int evt, int r) {
        fireEvent(new StackEvent(stack, null, ps, evt));
    }

    public void addListener(StackListener listener) {
        if (!listeners.contains(listener))
            listeners.add(listener);
    }

    public void removeListener(StackListener listener) {
        listeners.remove(listener);
    }
}
src/main/java/org/centum/android/model/Card.java
public class Card implements Externalizable {

    public static final int EVENT_TITLE_CHANGED = 0;
    public static final int EVENT_DETAILS_CHANGED = 1;
    public static final int EVENT_ATTACHMENT_IS_DETAILS_CHANGED = 2;
    public static final int EVENT_ATTACHMENT_CHANGED = 3;
    public static final int EVENT_SELECTION_CHANGED = 4;

    private static final long serialVersionUID = -982362622290325719L;

    private static final String DEBUG_TAG = "Card";
    private static final int externalizableVersion = 3;
    private final List<CardListener> listeners = new ArrayList<CardListener>();

    private String title = "";
    private String details = "";
    private String attachment = null;
    private boolean selected = false;
    private boolean attachmentPartOfDetails = false;
    private int quizletTermID = -1;
    private long sqlID = -1;

    public Card() {

    }

    public Card(String title) {
        this.title = title;
    }

    public Card(String title, String details) {
        this.title = title;
        this.details = details;
        if (details.length() > 1000) {
            this.details = details.substring(0, 1000);
        }
    }

    public String getDetails() {
        return details;
    }

    public void setDetails(String details) {
        this.details = details;
        if (details.length() > 1000) {
            this.details = details.substring(0, 1000);
        }
        fireEvent(this, EVENT_DETAILS_CHANGED);
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
        fireEvent(this, EVENT_TITLE_CHANGED);
    }

    public boolean isSelected() {
        return selected;
    }

    public void setSelected(boolean selected) {
        this.selected = selected;
        fireEvent(this, EVENT_SELECTION_CHANGED);
    }

    public boolean hasAttachment() {
        return getAttachment() != null;
    }

    public String getAttachment() {
        return attachment;
    }

    public void setAttachment(String attachment) {
        this.attachment = attachment;
        fireEvent(this, EVENT_SELECTION_CHANGED);
    }

    public boolean isAttachmentPartOfDetails() {
        return attachmentPartOfDetails;
    }

    public void setAttachmentPartOfDetails(boolean attachmentPartOfDetails) {
        this.attachmentPartOfDetails = attachmentPartOfDetails;
        fireEvent(this, EVENT_ATTACHMENT_IS_DETAILS_CHANGED);
    }

    public String toString() {
        return getTitle();
    }

    public void addListener(CardListener listener) {
        listeners.add(listener);
    }

    public void removeListener(CardListener listener) {
        listeners.remove(listener);
    }

    @Override
    public void readExternal(ObjectInput objectInput) throws IOException, ClassNotFoundException {
        int version = objectInput.readInt();
        title = objectInput.readUTF();
        details = objectInput.readUTF();
        if (objectInput.readBoolean())
            attachment = objectInput.readUTF();
        if (version > 1) {
            attachmentPartOfDetails = objectInput.readBoolean();
        }
        if (version > 2) {
            quizletTermID = objectInput.readInt();
        }
    }

    @Override
    public void writeExternal(ObjectOutput objectOutput) throws IOException {
        objectOutput.writeInt(externalizableVersion);
        objectOutput.writeUTF(title);
        objectOutput.writeUTF(details);
        objectOutput.writeBoolean(hasAttachment());
        if (hasAttachment())
            objectOutput.writeUTF(attachment);
        objectOutput.writeBoolean(attachmentPartOfDetails);
        objectOutput.writeInt(quizletTermID);
    }

    @Override
    public Card clone() {
        Card card = new Card(getTitle(), getDetails());
        card.setAttachment(getAttachment());
        card.setAttachmentPartOfDetails(isAttachmentPartOfDetails());
        card.setQuizletTermID(getQuizletTermID());
        return card;
    }

    public int getQuizletTermID() {
        return quizletTermID;
    }

    public void setQuizletTermID(int quizletTermID) {
        this.quizletTermID = quizletTermID;
    }

    public long getSqlID() {
        return sqlID;
    }

    public void setSqlID(long sqlID) {
        this.sqlID = sqlID;
    }

    private void fireEvent(Card card, int event) {
        fireEvent(new CardEvent(card, event));
    }

    private void fireEvent(CardEvent event) {
        for (CardListener listener : listeners) {
            listener.eventFired(event);
        }
    }
}
src/main/java/org/centum/android/model/events/PlaySessionEvent.java
public class PlaySessionEvent {
    private final PlaySession source;
    private final Card target;
    private final int event;

    public PlaySessionEvent(PlaySession source, Card target, int event) {
        this.source = source;
        this.event = event;
        this.target = target;
    }

    public Card getTarget() {
        return target;
    }

    public PlaySession getSource() {
        return source;
    }

    public int getEvent() {
        return event;
    }

}
src/main/java/org/centum/android/model/events/CardEvent.java
public class CardEvent {

    private final Card source;
    private final int event;

    public CardEvent(Card source, int event) {
        this.source = source;
        this.event = event;
    }

    public Card getSource() {
        return source;
    }

    public int getEvent() {
        return event;
    }

}
src/main/java/org/centum/android/model/events/StackListener.java
public interface StackListener {

    public void eventFired(StackEvent evt);

}
src/main/java/org/centum/android/model/events/StackManagerListener.java
public interface StackManagerListener {

    public void eventFired(StackManagerEvent evt);

}
src/main/java/org/centum/android/model/play/PlaySession.java
public class PlaySession implements Externalizable {

    public static final int EVENT_NAME_CHANGED = 0;
    public static final int EVENT_TIME_CHANGED = 1;
    public static final int EVENT_ENABLED_CHANGED = 2;
    public static final int EVENT_STATS_CHANGED = 3;
    public static final int EVENT_SESSION_SETTINGS_CHANGED = 4;
    public static final int ANS_NONE = 0;
    public static final int ANS_CORRECT = 1;
    public static final int ANS_WRONG = 2;
    private static final long serialVersionUID = 500881258321170619L;
    private static final String DEBUG_TAG = "PlaySession";
    private static final int externalizableVersion = 1;
    private final HashMap<Card, Integer> stats = new HashMap<Card, Integer>();
    private final List<PlaySessionListener> playSessionListeners = new ArrayList<PlaySessionListener>();
    private transient SessionSettings sessionSettings = null;
    private long time = 1;
    private String name;
    private boolean enabled = true;
    private long sqlID = -1;

    public PlaySession() {
        time = System.currentTimeMillis();
        name = "Play Session";
    }

    public PlaySession(String name) {
        this.name = name;
        time = System.currentTimeMillis();
    }

    public boolean hasDefaultName() {
        return name.equals("Play Session");
    }

    public boolean containsSessionStat(Card card) {
        return stats.containsKey(card);
    }

    public int getSessionStat(Card card) {
        if (!containsSessionStat(card)) {
            return ANS_NONE;
        }
        return stats.get(card);
    }

    public void setSessionStat(Card card, int state) {
        this.stats.put(card, state);
        if (state == ANS_NONE) {
            removeCard(card);
        } else {
            fireEvent(card, EVENT_STATS_CHANGED);
        }
    }

    public void removeCard(Card card) {
        stats.remove(card);
        fireEvent(card, EVENT_STATS_CHANGED);
    }

    public boolean isEmpty() {
        for (Card card : stats.keySet()) {
            if (getSessionStat(card) != ANS_NONE) {
                return false;
            }
        }
        return true;
    }

    public Date getDate() {
        return new Date(getTime());
    }

    public long getTime() {
        return time;
    }

    public void setTime(long time) {
        this.time = time;
        fireEvent(null, EVENT_TIME_CHANGED);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        fireEvent(null, EVENT_NAME_CHANGED);
    }

    public Card[] getCards() {
        return stats.keySet().toArray(new Card[stats.keySet().size()]);
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
        fireEvent(null, EVENT_ENABLED_CHANGED);
    }

    public SessionSettings getSessionSettings() {
        if (sessionSettings == null) {
            setSessionSettings(new SessionSettings());
        }
        return sessionSettings;
    }

    public void setSessionSettings(SessionSettings sessionSettings) {
        this.sessionSettings = sessionSettings;
        fireEvent(null, EVENT_SESSION_SETTINGS_CHANGED);
    }

    public void fireAll() {
        for (Card card : stats.keySet()) {
            fireEvent(card, EVENT_STATS_CHANGED);
        }
    }

    public void addListener(PlaySessionListener playSessionListener) {
        if (!playSessionListeners.contains(playSessionListener))
            playSessionListeners.add(playSessionListener);
    }

    public void removeListener(PlaySessionListener playSessionListener) {
        playSessionListeners.remove(playSessionListener);
    }

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

    public String getId() {
        return time + name;
    }

    @Override
    public void readExternal(ObjectInput objectInput) throws IOException, ClassNotFoundException {
        int version = objectInput.readInt();
        time = objectInput.readLong();
        name = objectInput.readUTF();
        enabled = objectInput.readBoolean();
        int size = objectInput.readInt();

        for (int i = 0; i < size; i++) {
            Card card = (Card) objectInput.readObject();
            int ans = objectInput.readInt();
            stats.put(card, ans);
        }
    }

    @Override
    public void writeExternal(ObjectOutput objectOutput) throws IOException {
        objectOutput.writeInt(externalizableVersion);
        objectOutput.writeLong(time);
        objectOutput.writeUTF(name);
        objectOutput.writeBoolean(enabled);
        objectOutput.writeInt(stats.size());

        Object cards[] = stats.keySet().toArray();
        for (int i = 0; i < stats.size(); i++) {
            objectOutput.writeObject(cards[i]);
            objectOutput.writeInt(stats.get(cards[i]));
        }
    }

    public long getSqlID() {
        return sqlID;
    }

    public void setSqlID(long sqlID) {
        this.sqlID = sqlID;
    }

    private void fireEvent(Card card, int event) {
        fireEvent(new PlaySessionEvent(this, card, event));
    }

    private void fireEvent(PlaySessionEvent playSessionEvent) {
        for (PlaySessionListener listener : playSessionListeners) {
            listener.eventFired(playSessionEvent);
        }
    }
}


Question: import org.centum.android.model.Card;
import org.centum.android.model.Stack;
import org.centum.android.model.StackManager;
import org.centum.android.model.events.CardEvent;
import org.centum.android.model.events.CardListener;
import org.centum.android.model.events.PlaySessionEvent;
import org.centum.android.model.events.PlaySessionListener;
import org.centum.android.model.events.StackEvent;
import org.centum.android.model.events.StackListener;
import org.centum.android.model.events.StackManagerEvent;
import org.centum.android.model.events.StackManagerListener;
import org.centum.android.model.play.PlaySession;
                log("Inserted stack " + evt.getTarget().getName());
                break;
            case StackManager.EVENT_STACK_REMOVED:
                removeListenersFromStack(evt.getTarget());
                dbHelper.deleteStack(evt.getTarget());
                log("Deleted stack " + evt.getTarget().getName());
                break;
            case StackManager.EVENT_ARCHIVED_STACK_ADDED:
                addListenersToStack(evt.getTarget());
                dbHelper.updateStack(evt.getTarget());
                log("Inserted archived stack " + evt.getTarget().getName());
                break;
            case StackManager.EVENT_ARCHIVED_STACK_REMOVED:
                removeListenersFromStack(evt.getTarget());
                dbHelper.deleteStack(evt.getTarget());
                log("Deleted archived stack " + evt.getTarget().getName());
                break;
            case StackManager.EVENT_STACK_ARCHIVE_STATUS_CHANGED:
                dbHelper.updateStackProperties(evt.getTarget());
                log("Updated stack properties (archive status) :" + evt.getTarget().getName());
        }
    }

    @Override
    public void eventFired(StackEvent evt) {
        switch (evt.getEvent()) {
            case Stack.EVENT_CARD_ADDED:
                evt.getTarget().addListener(this);
                dbHelper.updateCard(evt.getSource(), evt.getTarget());
                log("Inserted card " + evt.getTarget().getTitle());
                break;
            case Stack.EVENT_CARD_REMOVED:
                evt.getTarget().removeListener(this);
                dbHelper.deleteCard(evt.getTarget());
                log("Deleted card " + evt.getTarget().getTitle());
                break;
            case Stack.EVENT_PLAY_SESSION_ADDED:
                evt.getPsTarget().addListener(this);
                dbHelper.updatePlaySession(evt.getSource(), evt.getPsTarget());
                log("Updated playsession " + evt.getPsTarget().getName());
                break;
            case Stack.EVENT_PLAY_SESSION_REMOVED:
                evt.getPsTarget().removeListener(this);
                dbHelper.deletePlaySession(evt.getPsTarget());
                log("Deleted playsession " + evt.getPsTarget().getName());
                break;
            case Stack.EVENT_ARCHIVED_CARD_ADDED:
                evt.getTarget().addListener(this);
                dbHelper.updateCard(evt.getSource(), evt.getTarget());
                log("Updated archived card " + evt.getTarget().getTitle());
                break;
            case Stack.EVENT_ARCHIVED_CARD_REMOVED:
                evt.getTarget().removeListener(this);
                dbHelper.deleteCard(evt.getTarget());
                log("Deleted archived card " + evt.getTarget().getTitle());
                break;
            case Stack.EVENT_CARD_ARCHIVE_STATUS_CHANGED:
                dbHelper.updateCard(evt.getSource(), evt.getTarget());
                log("Updated card (archive status) " + evt.getTarget().getTitle());
                break;
            case Stack.EVENT_SELECTION_CHANGED:
                //Selection isn't persisted
                break;
            default:
                dbHelper.updateStackProperties(evt.getSource());
                log("Updated properties stack " + evt.getSource().getName());
                break;
        }
    }

    @Override
    public void eventFired(CardEvent evt) {
        if (evt.getEvent() != Card.EVENT_SELECTION_CHANGED) {
            for (Stack stack : StackManager.get().getStackList()) {
                if (stack.containsCard(evt.getSource())) {
                    dbHelper.updateCard(stack, evt.getSource());
                    log("Updated card " + evt.getSource().getTitle());
                    return;
                }
            }
            for (Stack stack : StackManager.get().getArchivedStackList()) {
                if (stack.containsCard(evt.getSource())) {
                    dbHelper.updateCard(stack, evt.getSource());
                    log("Updated card " + evt.getSource().getTitle());
                    return;
                }
            }

            log("Unexpected condition! Card is not a part of any stack");
        }
    }

    @Override
    public void eventFired(PlaySessionEvent evt) {
        for (Stack stack : StackManager.get().getStackList()) {
            if (stack.containsPlaySession(evt.getSource())) {
                dbHelper.updatePlaySession(stack, evt.getSource());
                log("Updated playsession " + evt.getSource().getName());
                return;
            }
        }
        for (Stack stack : StackManager.get().getArchivedStackList()) {
            if (stack.containsPlaySession(evt.getSource())) {
                dbHelper.updatePlaySession(stack, evt.getSource());
                log("Updated playsession " + evt.getSource().getName());
                return;
            }
        }

        log("Unexpected condition! PlaySession is not a part of any stack");
    }

    private void addListenersToStack(Stack stack) {
        stack.addListener(this);
        for (Card card : stack.getCardList()) {
            card.addListener(this);
        }
        for (Card card : stack.getArchivedCards()) {
            card.addListener(this);
        }
Answer:         for (PlaySession playSession : stack.getPlaySessions()) {