src/main/java/org/centum/android/stats/StatsActivity.java
public class StatsActivity extends Activity {

    private GridView statsGridView;
    private StatsListAdapter listAdapter;
    private TextView emptyTextView;
    private ImageView emptyImageView;
    private Stack stack;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setTheme(Themes.get(this).getCurrentTheme());
        setContentView(R.layout.stats_activity);

        statsGridView = (GridView) findViewById(R.id.stats_list);
        emptyTextView = (TextView) findViewById(R.id.textView);
        emptyImageView = (ImageView) findViewById(R.id.imageView);

        ViewTreeObserver viewTreeObserver = statsGridView.getViewTreeObserver();
        if (viewTreeObserver.isAlive()) {
            viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    updateColumns();
                }
            });
        }

        String stackName = getIntent().getExtras().getString("stack");
        stack = StackManager.get().getStack(stackName);

        if (stack != null && stack.getNumberOfPlaySessions() > 0) {
            setTitle(stackName + " Stats");
            listAdapter = new StatsListAdapter(this, R.layout.stats_card_item, stack, stack.getPlayStatsWithEnabledSessions());
            statsGridView.setAdapter(listAdapter);
            emptyImageView.setVisibility(View.GONE);
            emptyTextView.setVisibility(View.GONE);
        } else {
            emptyImageView.setVisibility(View.VISIBLE);
            emptyTextView.setVisibility(View.VISIBLE);
        }


    }

    @Override
    public void onStart() {
        super.onStart();
        if (!PreferenceManager.getDefaultSharedPreferences(this).getBoolean(SettingsActivity.KEY_PREF_ANALYTICS_OPTOUT, false))
            EasyTracker.getInstance(this).activityStart(this);
    }

    @Override
    public void onStop() {
        super.onStop();
        if (!PreferenceManager.getDefaultSharedPreferences(this).getBoolean(SettingsActivity.KEY_PREF_ANALYTICS_OPTOUT, false))
            EasyTracker.getInstance(this).activityStop(this);
    }

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

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                onBackPressed();
                return true;
            case R.id.action_edit_sessions:
                editSessionsActionPerformed();
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onResume() {
        super.onResume();
        updateAdapter();
        updateColumns();
    }

    public void updateColumns() {
        float density = getResources().getDisplayMetrics().density;
        if (statsGridView.getWidth() > Math.round((float) 400 * density)) {
            statsGridView.setNumColumns(Math.round(statsGridView.getWidth() / (400 * density)));
        } else {
            statsGridView.setNumColumns(1);
        }
    }


    private void editSessionsActionPerformed() {
        if (stack != null && stack.getNumberOfPlaySessions() > 0) {
            Intent intent = new Intent(this, SessionActivity.class);
            intent.putExtra("stack", stack.getName());
            startActivity(intent);
        }
    }

    private void updateAdapter() {
        if (listAdapter != null && stack != null)
            listAdapter.setPlayStats(stack.getPlayStatsWithEnabledSessions());
    }

}
src/main/java/org/centum/android/utils/Clipboard.java
public class Clipboard {

    private static Clipboard instance = null;
    private List<Card> contents = new LinkedList<Card>();

    public void setContents(List<Card> contents) {
        this.contents = contents;
    }

    public List<Card> getContents() {
        List<Card> cards = new LinkedList<Card>();
        for (Card c : contents) {
            cards.add(c.clone());
        }
        return cards;
    }

    public boolean hasContents() {
        return contents.size() > 0;
    }

    public void clearClipboard() {
        contents.clear();
    }

    public static Clipboard get() {
        if (instance == null) {
            instance = new Clipboard();
        }
        return instance;
    }

}
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/settings/SettingsActivity.java
public class SettingsActivity extends Activity implements SharedPreferences.OnSharedPreferenceChangeListener {

    public static final String KEY_PREF_THEME = "pref_theme";
    public static final String KEY_PREF_STACK_SWIPE_DELETES = "pref_key_stack_swipe_deletes";
    public static final String KEY_PREF_CARD_SWIPE_DELETES = "pref_key_card_swipe_deletes";
    public static final String KEY_PREF_CLEAN_ATTACHMENTS = "pref_clean_attachments";
    public static final String KEY_PREF_RESET = "pref_reset";
    public static final String KEY_PREF_PLAY_AUTO_ADVANCE = "pref_key_play_auto_advance";
    public static final String KEY_PREF_PLAY_SHUFFLE_TEST = "pref_key_play_shuffle_test";
    public static final String KEY_PREF_EXPORT = "pref_key_export";
    public static final String KEY_PREF_IMPORT = "pref_key_import";
    public static final String KEY_PREF_CARD_TAP_PREVIEW = "pref_key_card_tap_preview";
    public static final String KEY_PREF_ANALYTICS_OPTOUT = "pref_key_analytics_optout";
    public static final String KEY_PREF_CLEAN_EMPTY_MEMBERS = "pref_clean_empty_members";
    public static final String KEY_PREF_ADD_SAMPLE_STACK = "pref_add_sample_stack";
    public static final String KEY_PREF_CARD_CROP_DRAWING = "pref_key_card_crop_drawing";
    public static final String KEY_PREF_UPGRADE = "pref_key_card_crop_drawing";
    public static final String KEY_PREF_BACKUP_TIME = "pref_key_backup_time";
    public static final String KEY_PREF_PLAY_MARGIN_ADVANCE = "pref_key_play_margin_advance";
    public static final String KEY_PREF_WRITE_MIN_SIMILARITY = "pref_key_write_min_similarity";
    public static final String KEY_PREF_USE_IMMERSIVE = "pref_key_immersive";
    public static final String KEY_PREF_VIEW_RELEASE_NOTES = "pref_key_release_notes";
    public static final String KEY_PREF_WRITE_IGNORE_CASE = "pref_key_write_ignore_case";

    private boolean restartOnBack = false;

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        setTheme(Themes.get(this).getCurrentTheme());
        super.onCreate(savedInstanceState);
        getFragmentManager().beginTransaction()
                .replace(android.R.id.content, new SettingsFragment())
                .commit();
        PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this);

        restartOnBack = getIntent().getBooleanExtra("themeChanged", false);
    }

    @Override
    public void onResume() {
        super.onResume();
        PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this);
    }

    @Override
    public void onPause() {
        super.onDestroy();
        PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                onBackPressed();
                return true;
        }
        return false;
    }

    @Override
    public void onBackPressed() {
        if (restartOnBack) {
            restartActivity();
        } else {
            super.onBackPressed();
        }
    }

    private void restartActivity() {
        Intent i = getBaseContext().getPackageManager()
                .getLaunchIntentForPackage(getBaseContext().getPackageName());
        i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        startActivity(i);
    }

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        if (key.equals(KEY_PREF_THEME)) {
            finish();
            Intent i = new Intent(this, this.getClass());
            i.putExtra("themeChanged", true);
            startActivity(i);
        }
    }
}
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/presentation/learn/LearnActivity.java
public class LearnActivity extends Activity implements View.OnClickListener {

    private ImageView closeImageButton;
    private ImageView editButton;
    private ViewPager viewPager;
    private LearnPagerAdapter learnPagerAdapter;
    private RelativeLayout swipeHint;
    private View rightMargin;
    private View leftMargin;
    private Stack stack;
    private boolean immersivePref = true;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setTheme(Themes.get(this).getCurrentTheme());
        setContentView(R.layout.learn_activity);
        if (getActionBar() != null) {
            getActionBar().hide();
        }

        leftMargin = findViewById(R.id.left_margin_view);
        rightMargin = findViewById(R.id.right_margin_view);
        viewPager = (ViewPager) findViewById(R.id.viewPager);
        editButton = (ImageView) findViewById(R.id.edit_imageButton);
        swipeHint = (RelativeLayout) findViewById(R.id.swipeHint_relativeLayout);
        editButton.setOnClickListener(this);
        closeImageButton = (ImageView) findViewById(R.id.close_imageButton);
        closeImageButton.setOnClickListener(this);

        swipeHint.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                swipeHint.setVisibility(View.GONE);
            }
        });

        setStack(StackManager.get().getStack(getIntent().getExtras().getString("stack")));
        if (stack != null && savedInstanceState != null) {
            if (savedInstanceState.getString("stack").equals(stack.getName())) {
                viewPager.setCurrentItem(savedInstanceState.getInt("page"));
            }
        }

        viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int i, float v, int i2) {

            }

            @Override
            public void onPageSelected(int i) {
                swipeHint.setVisibility(View.GONE);
            }

            @Override
            public void onPageScrollStateChanged(int i) {

            }
        });

        leftMargin.setOnClickListener(this);
        rightMargin.setOnClickListener(this);
        immersivePref = PreferenceManager.getDefaultSharedPreferences(this).getBoolean(SettingsActivity.KEY_PREF_USE_IMMERSIVE, true);
    }

    @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
        if (stack != null) {
            savedInstanceState.putInt("page", viewPager.getCurrentItem());
            savedInstanceState.putString("stack", stack.getName());
        }
    }

    private void setStack(Stack stack) {
        this.stack = stack;
        learnPagerAdapter = new LearnPagerAdapter(viewPager, stack);
        viewPager.setAdapter(learnPagerAdapter);
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if (immersivePref && hasFocus) {
            if (Build.VERSION.SDK_INT >= 16) {
                viewPager.setSystemUiVisibility(
                        View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                                | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                                | View.SYSTEM_UI_FLAG_FULLSCREEN
                                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
                );
            } else {
                viewPager.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
            }
        }
    }

    @Override
    public void onClick(View view) {
        if (view.getId() == R.id.close_imageButton) {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle("Stop Review Session?");
            builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialogInterface, int i) {
                    dialogInterface.dismiss();
                    onBackPressed();
                }
            });

            builder.setNegativeButton("No", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialogInterface, int i) {
                    dialogInterface.dismiss();
                }
            });
            builder.create().show();
        } else if (viewPager.getCurrentItem() > 0 && view.getId() == R.id.left_margin_view) {
            viewPager.setCurrentItem(viewPager.getCurrentItem() - 1, true);
        } else if (view.getId() == R.id.right_margin_view) {
            viewPager.setCurrentItem(viewPager.getCurrentItem() + 1, true);
        } else if (view.getId() == R.id.edit_imageButton) {
            CardDialogFragment cardDialogFragment = new CardDialogFragment();
            cardDialogFragment.setCard(stack, learnPagerAdapter.getCardAtPosition(viewPager.getCurrentItem()));
            cardDialogFragment.show(getFragmentManager(), "new_card");
        }
    }

    @Override
    public void onStart() {
        super.onStart();
        if (!PreferenceManager.getDefaultSharedPreferences(this).getBoolean(SettingsActivity.KEY_PREF_ANALYTICS_OPTOUT, false))
            EasyTracker.getInstance(this).activityStart(this);
    }

    @Override
    public void onStop() {
        super.onStop();
        if (!PreferenceManager.getDefaultSharedPreferences(this).getBoolean(SettingsActivity.KEY_PREF_ANALYTICS_OPTOUT, false))
            EasyTracker.getInstance(this).activityStop(this);
    }

}
src/main/java/org/centum/android/presentation/mixed/MixedActivity.java
public class MixedActivity extends AbstractPlayActivity {

    private MixedPagerAdapter pagerAdapter;

    public MixedActivity() {
        setImmersive(true);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        int currentOrientation = getResources().getConfiguration().orientation;
        if (currentOrientation == Configuration.ORIENTATION_LANDSCAPE) {
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
        } else {
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
        }
        this.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);

    }

    @Override
    protected void onStackSet() {
        pagerAdapter = new MixedPagerAdapter(this, getStack(), getPlaySession());
    }

    @Override
    public PagerAdapter getPagerAdapter() {
        return pagerAdapter;
    }
}
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/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/presentation/general/PlaySessionSettingsDialogFragment.java
public class PlaySessionSettingsDialogFragment extends DialogFragment implements View.OnClickListener {

    private Stack stack = null;
    private PlaySession playSession = new PlaySession();
    private EditText nameEditText;
    private EditText stackMinEditText, stackSecEditText;
    private EditText cardMinEditText, cardSecEditText;
    private CheckBox stackLimitCheckBox, cardLimitCheckBox;
    private CheckBox simpleCheckBox, multiCheckBox, writeCheckBox;
    private Button presetTimedButton, presetRapidButton, presetUntimedButton;
    private Button cancelButton, startButton;
    private TextView stackColon, cardColon;
    private DialogInterface.OnClickListener onClickListener;
    private int selectedOption = 0;

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

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        View view = inflater.inflate(R.layout.play_settings_dialog_fragment, container);
        getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);

        nameEditText = (EditText) view.findViewById(R.id.session_name_editText);
        stackMinEditText = (EditText) view.findViewById(R.id.stack_min_editText);
        stackSecEditText = (EditText) view.findViewById(R.id.stack_sec_editText);
        cardMinEditText = (EditText) view.findViewById(R.id.card_min_editText);
        cardSecEditText = (EditText) view.findViewById(R.id.card_sec_editText);
        stackLimitCheckBox = (CheckBox) view.findViewById(R.id.session_time_limit_checkBox);
        cardLimitCheckBox = (CheckBox) view.findViewById(R.id.card_time_limit_checkBox);
        simpleCheckBox = (CheckBox) view.findViewById(R.id.simple_checkBox);
        multiCheckBox = (CheckBox) view.findViewById(R.id.multi_checkBox);
        writeCheckBox = (CheckBox) view.findViewById(R.id.write_checkBox);
        presetTimedButton = (Button) view.findViewById(R.id.timed_test_button);
        presetRapidButton = (Button) view.findViewById(R.id.rapid_fire_button);
        presetUntimedButton = (Button) view.findViewById(R.id.untimed_button);
        stackColon = (TextView) view.findViewById(R.id.stack_colon_textView);
        cardColon = (TextView) view.findViewById(R.id.card_colon_textView);
        cancelButton = (Button) view.findViewById(R.id.cancel_button);
        startButton = (Button) view.findViewById(R.id.confirm_button);

        presetRapidButton.setOnClickListener(this);
        presetTimedButton.setOnClickListener(this);
        presetUntimedButton.setOnClickListener(this);
        cancelButton.setOnClickListener(this);
        startButton.setOnClickListener(this);

        cardMinEditText.setText("1");
        stackMinEditText.setText(stack.getNumberOfCards() + "");

        setStackTimeFieldsVisibility(false);
        setCardTimeFieldsVisibility(false);

        stackLimitCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                setStackTimeFieldsVisibility(isChecked);
            }
        });

        cardLimitCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                setCardTimeFieldsVisibility(isChecked);
            }
        });

        int num = 1;
        while (stack.containsPlaySession("Play Session (" + num + ")")) {
            num++;
        }
        nameEditText.setText("Play Session (" + num + ")");

        return view;
    }

    public PlaySession getPlaySession() {
        return playSession;
    }

    private boolean validate() {
        String name = nameEditText.getText().toString();
        boolean valid = true;
        if (stack != null && stack.containsPlaySession(name) || TextUtils.isEmpty(name)) {
            nameEditText.setError("Invalid name");
            valid = false;
        } else {
            nameEditText.setError(null);
        }
        if (!simpleCheckBox.isChecked() && !multiCheckBox.isChecked() && !writeCheckBox.isChecked()) {
            simpleCheckBox.setChecked(true);
            multiCheckBox.setChecked(true);
            writeCheckBox.setChecked(true);
        }
        if (stackLimitCheckBox.isChecked()) {
            int min = 0, sec = 0;
            try {
                min = Integer.parseInt(stackMinEditText.getText().toString());
                stackMinEditText.setError(null);
            } catch (Exception e) {
                stackMinEditText.setError("Invalid input");
                valid = false;
            }
            try {
                sec = Integer.parseInt(stackSecEditText.getText().toString());
                stackSecEditText.setError(null);
            } catch (Exception e) {
                stackSecEditText.setError("Invalid input");
                valid = false;
            }

            if (valid && (min <= 0 && sec <= 0)) {
                valid = false;
                stackMinEditText.setError("The time limit has to be > 0");
            } else {
                stackMinEditText.setError(null);

                if (min < 0) {
                    stackMinEditText.setError("This can't be less than 0");
                    valid = false;
                } else {
                    stackMinEditText.setError(null);
                }

                if (sec < 0) {
                    stackSecEditText.setError("This can't be less than 0");
                    valid = false;
                } else {
                    stackSecEditText.setError(null);
                }
            }

        } else {
            stackMinEditText.setError(null);
            stackSecEditText.setError(null);
        }
        if (cardLimitCheckBox.isChecked()) {
            int min = 0, sec = 0;
            try {
                min = Integer.parseInt(cardMinEditText.getText().toString());
                cardMinEditText.setError(null);
            } catch (Exception e) {
                cardMinEditText.setError("Invalid input");
                valid = false;
            }
            try {
                sec = Integer.parseInt(cardSecEditText.getText().toString());
                cardSecEditText.setError(null);
            } catch (Exception e) {
                cardSecEditText.setError("Invalid input");
                valid = false;
            }

            if (valid && (min <= 0 && sec <= 0)) {
                valid = false;
                cardMinEditText.setError("The time limit has to be > 0");
            } else {
                cardMinEditText.setError(null);

                if (min < 0) {
                    cardMinEditText.setError("This can't be less than 0");
                    valid = false;
                } else {
                    cardMinEditText.setError(null);
                }

                if (sec < 0) {
                    cardSecEditText.setError("This can't be less than 0");
                    valid = false;
                } else {
                    cardSecEditText.setError(null);
                }
            }

        } else {
            cardSecEditText.setError(null);
            cardMinEditText.setError(null);
        }

        return valid;
    }

    private void onConfirm() {
        if (validate()) {
            nameEditText.setError(null);
            boolean stackLimit = stackLimitCheckBox.isChecked();
            boolean cardLimit = cardLimitCheckBox.isChecked();
            SessionSettings sessionSettings = new SessionSettings(cardLimit, stackLimit,
                    true, getStackSeconds(), getCardSeconds(),
                    simpleCheckBox.isChecked(), multiCheckBox.isChecked(), writeCheckBox.isChecked());
            playSession.setSessionSettings(sessionSettings);

            if (onClickListener != null) {
                onClickListener.onClick(getDialog(), 0);
            }
        }
    }

    private int getStackSeconds() {
        int minutes;
        int seconds;
        try {
            if (TextUtils.isEmpty(stackMinEditText.getText().toString())) {
                minutes = 0;
            } else {
                minutes = Integer.parseInt(stackMinEditText.getText().toString());
            }
            if (TextUtils.isEmpty(stackSecEditText.getText().toString())) {
                seconds = 0;
            } else {
                seconds = Integer.parseInt(stackSecEditText.getText().toString());
            }
            return minutes * 60 + seconds;
        } catch (Exception e) {
            return Integer.MAX_VALUE;
        }
    }

    private int getCardSeconds() {
        int minutes;
        int seconds;
        try {
            if (TextUtils.isEmpty(cardMinEditText.getText().toString())) {
                minutes = 0;
            } else {
                minutes = Integer.parseInt(cardMinEditText.getText().toString());
            }
            if (TextUtils.isEmpty(cardSecEditText.getText().toString())) {
                seconds = 0;
            } else {
                seconds = Integer.parseInt(cardSecEditText.getText().toString());
            }
            return minutes * 60 + seconds;
        } catch (Exception e) {
            return Integer.MAX_VALUE;
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.cancel_button:
                playSession = null;
                dismiss();
                break;
            case R.id.confirm_button:
                onConfirm();
                break;
            case R.id.rapid_fire_button:
                onRapidFireButtonAction();
                break;
            case R.id.timed_test_button:
                onTimedButtonAction();
                break;
            case R.id.untimed_button:
                onUntimedButtonAction();
                break;
        }
    }

    private void onRapidFireButtonAction() {
        stackLimitCheckBox.setChecked(false);
        stackMinEditText.setText((stack.getNumberOfCards()) + "");
        stackSecEditText.setText("0");
        cardLimitCheckBox.setChecked(true);
        cardMinEditText.setText("0");
        cardSecEditText.setText("10");
        selectedOption = 2;
    }

    private void onTimedButtonAction() {
        stackLimitCheckBox.setChecked(true);
        stackMinEditText.setText(stack.getNumberOfCards() + "");
        stackSecEditText.setText("0");
        cardLimitCheckBox.setChecked(true);
        cardMinEditText.setText("1");
        cardSecEditText.setText("0");
        selectedOption = 1;
    }

    private void onUntimedButtonAction() {
        cardLimitCheckBox.setChecked(false);
        stackLimitCheckBox.setChecked(false);
        selectedOption = 0;
    }


    private void setStackTimeFieldsVisibility(boolean visibility) {
        stackColon.setVisibility(visibility ? View.VISIBLE : View.GONE);
        stackMinEditText.setVisibility(visibility ? View.VISIBLE : View.GONE);
        stackSecEditText.setVisibility(visibility ? View.VISIBLE : View.GONE);
    }

    private void setCardTimeFieldsVisibility(boolean visibility) {
        cardColon.setVisibility(visibility ? View.VISIBLE : View.GONE);
        cardMinEditText.setVisibility(visibility ? View.VISIBLE : View.GONE);
        cardSecEditText.setVisibility(visibility ? View.VISIBLE : View.GONE);
    }

    public void setOnClickListener(DialogInterface.OnClickListener listener) {
        onClickListener = listener;
    }

    @Override
    public void onPause() {
        super.onPause();
        if (getActivity() != null) {
            getActivity().getSharedPreferences("session_settings", 0).edit()
                    .putBoolean("stack_limit", stackLimitCheckBox.isChecked())
                    .putBoolean("card_limit", cardLimitCheckBox.isChecked())
                    .putString("stack_min", stackMinEditText.getText().toString())
                    .putString("stack_sec", stackSecEditText.getText().toString())
                    .putString("card_min", cardMinEditText.getText().toString())
                    .putString("card_sec", cardSecEditText.getText().toString())
                    .putBoolean("simple", simpleCheckBox.isChecked())
                    .putBoolean("multi", multiCheckBox.isChecked())
                    .putBoolean("write", writeCheckBox.isChecked())
                    .putInt("preset", selectedOption).commit();
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        if (getActivity() != null) {
            SharedPreferences prefs = getActivity().getSharedPreferences("session_settings", 0);
            stackLimitCheckBox.setChecked(prefs.getBoolean("stack_limit", false));
            cardLimitCheckBox.setChecked(prefs.getBoolean("card_limit", false));
            stackMinEditText.setText(prefs.getString("stack_min", "10"));
            cardMinEditText.setText(prefs.getString("card_min", "0"));
            stackSecEditText.setText(prefs.getString("stack_sec", "0"));
            cardSecEditText.setText(prefs.getString("card_sec", "10"));
            simpleCheckBox.setChecked(prefs.getBoolean("simple", true));
            multiCheckBox.setChecked(prefs.getBoolean("multi", true));
            writeCheckBox.setChecked(prefs.getBoolean("write", true));
            int opt = prefs.getInt("preset", 0);
            switch (opt) {
                case 0:
                    onUntimedButtonAction();
                    break;
                case 1:
                    onTimedButtonAction();
                    break;
                case 2:
                    onRapidFireButtonAction();
                    break;
            }
        }
    }

    public Stack getStack() {
        return stack;
    }

    public void setStack(Stack stack) {
        this.stack = stack;
    }
}
src/main/java/org/centum/android/sql/StacksDatabaseHelper.java
public class StacksDatabaseHelper extends SQLiteOpenHelper {

    private static final int DATABASE_VERSION = 2;
    private static final String DATABASE_NAME = "stacks.db";
    private static StacksDatabaseHelper instance = null;
    private final Context context;
    private boolean isSaving = false;

    private StacksDatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
        this.context = context;
    }

    public static StacksDatabaseHelper get(Context context) {
        if (instance == null) {
            instance = new StacksDatabaseHelper(context);
        }
        return instance;
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        log("onCreate");
        sqLiteDatabase.execSQL(StacksDatabaseContract.StackEntry.SQL_CREATE_TABLE);
        sqLiteDatabase.execSQL(StacksDatabaseContract.CardEntry.SQL_CREATE_TABLE);
        sqLiteDatabase.execSQL(StacksDatabaseContract.PlaySessionEntry.SQL_CREATE_TABLE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i2) {
        if (i == 1 && i2 == 2) {
            //Add column for quizlet term id
            log("Upgrading Database");
            sqLiteDatabase.execSQL(StacksDatabaseContract.CardEntry.SQL_ADD_TERM_ID_COLUMN);
        }
    }

    @Override
    public void onOpen(SQLiteDatabase db) {
        super.onOpen(db);
        if (!db.isReadOnly()) {
            db.execSQL("PRAGMA foreign_keys=ON;");
        }
    }

    private synchronized boolean isSaving() {
        return isSaving;
    }

    private synchronized void setSaving(boolean saving) {
        isSaving = saving;
    }

    public void executeBulkOperation(Runnable runnable) {
        StacksDatabaseHelper.get(null).getWritableDatabase().beginTransaction();
        runnable.run();
        getWritableDatabase().setTransactionSuccessful();
        getWritableDatabase().endTransaction();
    }

    public void saveStacks() {
        if (!isSaving()) {
            setSaving(true);
            new AsyncTask<Void, Void, Void>() {

                @Override
                protected Void doInBackground(Void... voids) {
                    long start = System.currentTimeMillis();
                    getWritableDatabase().beginTransaction();
                    for (Stack stack : StackManager.get().getStackList()) {
                        updateStack(stack);
                    }
                    for (Stack stack : StackManager.get().getArchivedStacksList()) {
                        updateStack(stack);
                    }
                    deleteOrphanedStacksInDB();
                    getWritableDatabase().setTransactionSuccessful();
                    getWritableDatabase().endTransaction();
                    long stop = System.currentTimeMillis();
                    log("Saved SQL DB in " + (stop - start) + "ms");
                    setSaving(false);
                    return null;
                }
            }.execute();
        }
    }

    public LoadedData loadStacks() {
        LoadedData loadedData = new LoadedData();
        long start = System.currentTimeMillis();
        Cursor cursor = getReadableDatabase().query(StacksDatabaseContract.StackEntry.TABLE_NAME,
                null, null, null, null, null, null);
        cursor.moveToFirst();

        long sqlID;
        String name, description;
        int icon, quizletID;
        boolean isQuizlet, isArchived;
        Stack stack;

        while (!cursor.isAfterLast()) {
            sqlID = cursor.getLong(cursor.getColumnIndexOrThrow(StacksDatabaseContract.StackEntry._ID));
            name = cursor.getString(cursor.getColumnIndexOrThrow(StacksDatabaseContract.StackEntry.COLUMN_NAME));
            description = cursor.getString(cursor.getColumnIndexOrThrow(StacksDatabaseContract.StackEntry.COLUMN_DESCRIPTION));
            icon = cursor.getInt(cursor.getColumnIndexOrThrow(StacksDatabaseContract.StackEntry.COLUMN_ICON));
            isQuizlet = cursor.getInt(cursor.getColumnIndexOrThrow(StacksDatabaseContract.StackEntry.COLUMN_IS_QUIZLET)) == 1;
            quizletID = cursor.getInt(cursor.getColumnIndexOrThrow(StacksDatabaseContract.StackEntry.COLUMN_QUIZLET_ID));
            isArchived = cursor.getInt(cursor.getColumnIndexOrThrow(StacksDatabaseContract.StackEntry.COLUMN_IS_ARCHIVED)) == 1;

            stack = new Stack(name);
            stack.setSqlID(sqlID);
            stack.setDescription(description);
            stack.setIcon(icon);
            stack.setQuizletStack(isQuizlet);
            stack.setQuizletID(quizletID);

            List<Card> cardList = loadCardsForStack(sqlID);
            List<Card> archivedCardList = loadArchivedCardsForStack(sqlID);
            for (Card card : cardList) {
                stack.quickAddCard(card);
            }
            for (Card card : archivedCardList) {
                stack.quickAddArchivedCard(card);
            }

            List<PlaySession> playSessions = loadPlaySessionsForStack(stack);
            for (PlaySession playSession : playSessions) {
                stack.addPlaySession(playSession);
            }

            if (isArchived) {
                loadedData.addLoadedArchivedStack(stack);
            } else {
                loadedData.addLoadedStack(stack);
            }

            cursor.move(1);
        }
        cursor.close();
        long stop = System.currentTimeMillis();
        log("Loaded SQL DB in " + (stop - start) + "ms");
        if (EasyTracker.getInstance(context) != null) {
            if (!PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SettingsActivity.KEY_PREF_ANALYTICS_OPTOUT, false))
                EasyTracker.getInstance(context).send(MapBuilder.createTiming("data", stop - start, "load sql db", null).build());
        }
        return loadedData;
    }

    private void resetDB() {
        getWritableDatabase().execSQL(StacksDatabaseContract.StackEntry.SQL_DELETE_ENTRIES);
        getWritableDatabase().execSQL(StacksDatabaseContract.PlaySessionEntry.SQL_DELETE_ENTRIES);
        getWritableDatabase().execSQL(StacksDatabaseContract.CardEntry.SQL_DELETE_ENTRIES);
        onCreate(getWritableDatabase());
    }

    private void deleteOrphanedStacksInDB() {
        List<Long> stacksInDB = new LinkedList<Long>();

        Cursor cursor = getReadableDatabase().query(StacksDatabaseContract.StackEntry.TABLE_NAME,
                new String[]{StacksDatabaseContract.StackEntry._ID}, null, null, null, null, null);
        cursor.moveToFirst();
        while (!cursor.isAfterLast()) {
            stacksInDB.add(cursor.getLong(cursor.getColumnIndexOrThrow(StacksDatabaseContract.StackEntry._ID)));
            cursor.move(1);
        }
        cursor.close();

        for (Long stackID : stacksInDB) {
            if (!containsStackSqlID(stackID)) {
                getWritableDatabase().delete(StacksDatabaseContract.StackEntry.TABLE_NAME,
                        StacksDatabaseContract.StackEntry._ID + "=" + stackID, null);
                getWritableDatabase().delete(StacksDatabaseContract.PlaySessionEntry.TABLE_NAME,
                        StacksDatabaseContract.PlaySessionEntry.COLUMN_PARENT_STACK + "=" + stackID, null);
                getWritableDatabase().delete(StacksDatabaseContract.CardEntry.TABLE_NAME,
                        StacksDatabaseContract.CardEntry.COLUMN_PARENT_STACK + "=" + stackID, null);
            }
        }
    }

    private boolean containsStackSqlID(long stackID) {
        for (Stack s : StackManager.get().getStackList()) {
            if (s.getSqlID() == stackID) {
                return true;
            }
        }
        for (Stack s : StackManager.get().getArchivedStacksList()) {
            if (s.getSqlID() == stackID) {
                return true;
            }
        }
        return false;
    }

    private boolean containsCardSqlID(Stack stack, long cardID) {
        for (Card c : stack.getCardList()) {
            if (c.getSqlID() == cardID) {
                return true;
            }
        }
        return false;
    }

    private List<PlaySession> loadPlaySessionsForStack(Stack stack) {
        Cursor cursor = getWritableDatabase().query(StacksDatabaseContract.PlaySessionEntry.TABLE_NAME,
                null, StacksDatabaseContract.PlaySessionEntry.COLUMN_PARENT_STACK + "=" + stack.getSqlID()
                , null, null, null, null);

        List<PlaySession> playSessions = new ArrayList<PlaySession>(cursor.getCount());

        cursor.moveToFirst();
        while (!cursor.isAfterLast()) {
            int id = cursor.getInt(cursor.getColumnIndexOrThrow(StacksDatabaseContract.PlaySessionEntry._ID));
            String name = cursor.getString(cursor.getColumnIndexOrThrow(StacksDatabaseContract.PlaySessionEntry.COLUMN_NAME));
            long time = cursor.getLong(cursor.getColumnIndexOrThrow(StacksDatabaseContract.PlaySessionEntry.COLUMN_TIME));
            boolean enabled = cursor.getInt(cursor.getColumnIndexOrThrow(StacksDatabaseContract.PlaySessionEntry.COLUMN_ENABLED)) == 1;
            String card = cursor.getString(cursor.getColumnIndexOrThrow(StacksDatabaseContract.PlaySessionEntry.COLUMN_CARDS));
            String ans = cursor.getString(cursor.getColumnIndexOrThrow(StacksDatabaseContract.PlaySessionEntry.COLUMN_ANSWERS));
            String cards[] = card.split(",");
            String answers[] = ans.split(",");

            PlaySession playSession = new PlaySession(name);
            playSession.setTime(time);
            playSession.setEnabled(enabled);
            playSession.setSqlID(id);

            for (int i = 0; i < cards.length; i++) {
                try {
                    Card c = getCard(stack, Long.parseLong(cards[i]));
                    if (c != null) {
                        playSession.setSessionStat(c, Integer.parseInt(answers[i]));
                    }
                } catch (Exception e) {
                    if (!(e instanceof NumberFormatException))
                        e.printStackTrace();
                }
            }

            playSessions.add(playSession);
            cursor.move(1);
        }
        cursor.close();
        return playSessions;
    }

    private Card getCard(Stack stack, long sqlID) {
        for (Card c : stack.getCardList()) {
            if (c.getSqlID() == sqlID) {
                return c;
            }
        }
        return null;
    }

    private List<Card> loadArchivedCardsForStack(long sqlID) {
        Cursor cursor = getWritableDatabase().query(StacksDatabaseContract.CardEntry.TABLE_NAME,
                null, StacksDatabaseContract.CardEntry.COLUMN_PARENT_STACK + "=" + sqlID + " AND " +
                        StacksDatabaseContract.CardEntry.COLUMN_IS_ARCHIVED + "=" + 1, null, null, null, null
        );

        return loadCards(cursor);
    }

    private List<Card> loadCardsForStack(long sqlID) {
        Cursor cursor = getWritableDatabase().query(StacksDatabaseContract.CardEntry.TABLE_NAME,
                null, StacksDatabaseContract.CardEntry.COLUMN_PARENT_STACK + "=" + sqlID + " AND " +
                        StacksDatabaseContract.CardEntry.COLUMN_IS_ARCHIVED + "=" + 0, null, null, null, null
        );

        return loadCards(cursor);
    }

    private List<Card> loadCards(Cursor cursor) {
        List<Card> cards = new ArrayList<Card>(cursor.getCount());

        String name, details, attachment;
        int id, pos, quizletTermId;
        boolean isAttachmentDetail;
        Card card;

        cursor.moveToFirst();
        while (!cursor.isAfterLast()) {
            id = cursor.getInt(cursor.getColumnIndexOrThrow(StacksDatabaseContract.CardEntry._ID));
            name = cursor.getString(cursor.getColumnIndexOrThrow(StacksDatabaseContract.CardEntry.COLUMN_TITLE));
            details = cursor.getString(cursor.getColumnIndexOrThrow(StacksDatabaseContract.CardEntry.COLUMN_DETAILS));
            attachment = cursor.getString(cursor.getColumnIndexOrThrow(StacksDatabaseContract.CardEntry.COLUMN_ATTACHMENT));
            isAttachmentDetail = cursor.getInt(cursor.getColumnIndexOrThrow(StacksDatabaseContract.CardEntry.COLUMN_IS_ATTACHMENT_DETAIL)) == 1;
            //pos = cursor.getInt(cursor.getColumnIndexOrThrow(StacksDatabaseContract.CardEntry.COLUMN_POSITION));
            quizletTermId = cursor.getInt(cursor.getColumnIndexOrThrow(StacksDatabaseContract.CardEntry.COLUMN_QUIZLET_TERM_ID));

            card = new Card(name, details);
            card.setAttachment(attachment);
            card.setAttachmentPartOfDetails(isAttachmentDetail);
            card.setSqlID(id);
            card.setQuizletTermID(quizletTermId);

            cards.add(card);
            cursor.move(1);
        }
        cursor.close();

        return cards;
    }

    public boolean deletePlaySession(PlaySession playSession) {
        int ret = getWritableDatabase().delete(StacksDatabaseContract.PlaySessionEntry.TABLE_NAME,
                StacksDatabaseContract.PlaySessionEntry._ID + "=?", new String[]{playSession.getSqlID() + ""});
        if (ret > 0) {
            playSession.setSqlID(-1);
        }
        return ret > 0;
    }

    public boolean updatePlaySession(Stack stack, PlaySession playSession) {
        if (playSession.getSqlID() < 0) {
            return insertPlaySession(stack, playSession);
        } else {
            ContentValues contentValues = getPlaySessionContentValues(stack, playSession);
            int ret = getWritableDatabase().update(StacksDatabaseContract.PlaySessionEntry.TABLE_NAME, contentValues,
                    StacksDatabaseContract.PlaySessionEntry._ID + "=?", new String[]{playSession.getSqlID() + ""});
            return ret > 1;
        }
    }

    public boolean insertPlaySession(Stack stack, PlaySession playSession) {
        ContentValues contentValues = getPlaySessionContentValues(stack, playSession);
        long ret = getWritableDatabase().insert(StacksDatabaseContract.PlaySessionEntry.TABLE_NAME, null, contentValues);
        playSession.setSqlID(ret);
        return ret != -1;
    }

    public boolean deleteCard(Card card) {
        int ret = getWritableDatabase().delete(StacksDatabaseContract.CardEntry.TABLE_NAME,
                StacksDatabaseContract.CardEntry._ID + "=?", new String[]{card.getSqlID() + ""});
        if (ret > 0) {
            card.setSqlID(-1);
        }
        return ret > 0;
    }

    public boolean updateCard(Stack stack, Card card) {
        if (card.getSqlID() < 0) {
            return insertCard(stack, card);
        } else {
            ContentValues contentValues = getCardContentValues(stack, card);
            int ret = getWritableDatabase().update(StacksDatabaseContract.CardEntry.TABLE_NAME, contentValues,
                    StacksDatabaseContract.CardEntry._ID + "=?", new String[]{card.getSqlID() + ""});
            return ret > 1;
        }
    }

    public boolean insertCard(Stack stack, Card card) {
        ContentValues contentValues = getCardContentValues(stack, card);
        long ret = getWritableDatabase().insert(StacksDatabaseContract.CardEntry.TABLE_NAME, null, contentValues);
        card.setSqlID(ret);
        return ret != -1;
    }

    public boolean deleteStack(Stack stack) {
        int ret = getWritableDatabase().delete(StacksDatabaseContract.StackEntry.TABLE_NAME,
                StacksDatabaseContract.StackEntry._ID + "=?", new String[]{stack.getSqlID() + ""});
        if (ret > 0) {
            stack.setSqlID(-1);
        }
        return ret > 0;
    }

    public boolean updateStackProperties(Stack stack) {
        ContentValues contentValues = getStackContentValues(stack);
        int ret = getWritableDatabase().update(StacksDatabaseContract.StackEntry.TABLE_NAME, contentValues,
                StacksDatabaseContract.StackEntry._ID + "=?", new String[]{stack.getSqlID() + ""});
        return ret > 1;
    }

    public void updateStack(Stack stack) {
        if (stack.getSqlID() < 0) {
            insertStack(stack);
        } else {
            getWritableDatabase().beginTransaction();
            updateStackProperties(stack);
            for (Card card : stack.getCardList()) {
                updateCard(stack, card);
            }
            for (Card card : stack.getArchivedCards()) {
                updateCard(stack, card);
            }
            for (PlaySession playSession : stack.getPlaySessions()) {
                updatePlaySession(stack, playSession);
            }
            getWritableDatabase().setTransactionSuccessful();
            getWritableDatabase().endTransaction();
        }
    }

    public boolean insertStack(Stack stack) {
        ContentValues contentValues = getStackContentValues(stack);
        getWritableDatabase().beginTransaction();
        long ret = getWritableDatabase().insert(StacksDatabaseContract.StackEntry.TABLE_NAME, null, contentValues);
        stack.setSqlID(ret);
        for (Card card : stack.getCardList()) {
            insertCard(stack, card);
        }
        for (Card card : stack.getArchivedCards()) {
            insertCard(stack, card);
        }
        for (PlaySession playSession : stack.getPlaySessions()) {
            insertPlaySession(stack, playSession);
        }
        getWritableDatabase().setTransactionSuccessful();
        getWritableDatabase().endTransaction();
        return (ret != -1);
    }

    private ContentValues getPlaySessionContentValues(Stack stack, PlaySession playSession) {
        ContentValues contentValues = new ContentValues();
        contentValues.put(StacksDatabaseContract.PlaySessionEntry.COLUMN_NAME, playSession.getName());
        contentValues.put(StacksDatabaseContract.PlaySessionEntry.COLUMN_TIME, playSession.getTime());
        contentValues.put(StacksDatabaseContract.PlaySessionEntry.COLUMN_ENABLED, playSession.isEnabled() ? 1 : 0);
        contentValues.put(StacksDatabaseContract.PlaySessionEntry.COLUMN_PARENT_STACK, stack == null ? -1 : stack.getSqlID());

        StringBuilder cardsBuilder = new StringBuilder();
        StringBuilder answersBuilder = new StringBuilder();

        for (Card card : playSession.getCards()) {
            cardsBuilder.append(card.getSqlID());
            cardsBuilder.append(",");
            answersBuilder.append(playSession.getSessionStat(card) + ",");
            answersBuilder.append(",");
        }
        String cards = cardsBuilder.toString();
        String ans = answersBuilder.toString();
        if (cards.length() > 1) {
            cards = cards.substring(0, cards.length() - 1);
            ans = ans.substring(0, ans.length() - 1);
        }

        contentValues.put(StacksDatabaseContract.PlaySessionEntry.COLUMN_CARDS, cards);
        contentValues.put(StacksDatabaseContract.PlaySessionEntry.COLUMN_ANSWERS, ans);

        return contentValues;
    }

    private ContentValues getCardContentValues(Stack stack, Card card) {
        ContentValues contentValues = new ContentValues();
        contentValues.put(StacksDatabaseContract.CardEntry.COLUMN_TITLE, card.getTitle());
        contentValues.put(StacksDatabaseContract.CardEntry.COLUMN_DETAILS, card.getDetails());
        contentValues.put(StacksDatabaseContract.CardEntry.COLUMN_ATTACHMENT, card.getAttachment());
        contentValues.put(StacksDatabaseContract.CardEntry.COLUMN_IS_ATTACHMENT_DETAIL, card.isAttachmentPartOfDetails() ? 1 : 0);
        if (stack != null) {
            contentValues.put(StacksDatabaseContract.CardEntry.COLUMN_IS_ARCHIVED, stack.getArchivedCards().contains(card) ? 1 : 0);
            contentValues.put(StacksDatabaseContract.CardEntry.COLUMN_PARENT_STACK, stack.getSqlID());
            contentValues.put(StacksDatabaseContract.CardEntry.COLUMN_POSITION, stack.getCardPosition(card));
        }
        contentValues.put(StacksDatabaseContract.CardEntry.COLUMN_QUIZLET_TERM_ID, card.getQuizletTermID());
        return contentValues;
    }

    private ContentValues getStackContentValues(Stack stack) {
        ContentValues contentValues = new ContentValues();
        contentValues.put(StacksDatabaseContract.StackEntry.COLUMN_NAME, stack.getName());
        contentValues.put(StacksDatabaseContract.StackEntry.COLUMN_DESCRIPTION, stack.getDescription());
        contentValues.put(StacksDatabaseContract.StackEntry.COLUMN_ICON, stack.getIcon());
        contentValues.put(StacksDatabaseContract.StackEntry.COLUMN_IS_QUIZLET, stack.isQuizletStack() ? 1 : 0);
        contentValues.put(StacksDatabaseContract.StackEntry.COLUMN_QUIZLET_ID, stack.getQuizletID());
        contentValues.put(StacksDatabaseContract.StackEntry.COLUMN_IS_ARCHIVED, StackManager.get().getArchivedStack(stack.getName()) == null ? 0 : 1);
        return contentValues;
    }

    private void log(String string) {
        Log.d("StacksDatabaseHelper", string);
    }
}
src/main/java/com/cocosw/undobar/UndoBarController.java
public class UndoBarController extends LinearLayout {

    public static UndoBarStyle UNDOSTYLE = new UndoBarStyle(
            R.drawable.ic_undobar_undo, R.string.undo);

    public static UndoBarStyle RETRYSTYLE = new UndoBarStyle(R.drawable.ic_retry,
            R.string.retry, -1);

    public static UndoBarStyle MESSAGESTYLE = new UndoBarStyle(-1, -1, 5000);

    public interface UndoListener {
        void onUndo(Parcelable token);
    }

    private final TextView mMessageView;
    private final TextView mButton;
    private final Handler mHideHandler = new Handler();
    private UndoListener mUndoListener;
    private UndoBarStyle style = UndoBarController.UNDOSTYLE;

    // State objects
    private Parcelable mUndoToken;

    private CharSequence mUndoMessage;
    private final Runnable mHideRunnable = new Runnable() {
        @Override
        public void run() {
            hideUndoBar(false);
        }
    };

    private UndoBarController(final Context context, final AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.undobar, this, true);
        mMessageView = (TextView) findViewById(R.id.undobar_message);
        mButton = (TextView) findViewById(R.id.undobar_button);
        mButton.setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(final View view) {
                        if (mUndoListener != null) {
                            mUndoListener.onUndo(mUndoToken);
                        }
                        hideUndoBar(false);
                    }
                });

        hideUndoBar(true);
    }

    /**
     * Get callback listener
     *
     * @return
     */
    public UndoListener getUndoListener() {
        return mUndoListener;
    }

    private void hideUndoBar(final boolean immediate) {
        mHideHandler.removeCallbacks(mHideRunnable);
        mUndoToken = null;
        if (immediate) {
            setVisibility(View.GONE);
        } else {
            clearAnimation();
            startAnimation(UndoBarController.outToBottomAnimation(null));
            setVisibility(View.GONE);
        }
    }

    private static Animation outToBottomAnimation(
            final android.view.animation.Animation.AnimationListener animationlistener) {
        final TranslateAnimation translateanimation = new TranslateAnimation(2,
                0F, 2, 0F, 2, 0F, 2, 1F);
        translateanimation.setDuration(500L);
        translateanimation.setInterpolator(new AnticipateOvershootInterpolator(
                1.0f));
        translateanimation.setAnimationListener(animationlistener);
        return translateanimation;
    }

    @Override
    protected Parcelable onSaveInstanceState() {
        final Bundle outState = new Bundle();
        outState.putCharSequence("undo_message", mUndoMessage);
        outState.putParcelable("undo_token", mUndoToken);
        return outState;
    }

    @Override
    protected void onRestoreInstanceState(final Parcelable state) {
        if (state instanceof Bundle) {
            final Bundle bundle = (Bundle) state;
            mUndoMessage = bundle.getCharSequence("undo_message");
            mUndoToken = bundle.getParcelable("undo_token");
            return;
        }
        super.onRestoreInstanceState(state);
    }

    private void setUndoListener(final UndoListener mUndoListener) {
        this.mUndoListener = mUndoListener;
    }

    private void showUndoBar(final boolean immediate,
                             final CharSequence message, final Parcelable undoToken) {
        mUndoToken = undoToken;
        mUndoMessage = message;
        mMessageView.setText(mUndoMessage);

        if (style != null) {
            if (style.titleRes > 0) {
                mButton.setVisibility(View.VISIBLE);
                findViewById(R.id.undobar_divider).setVisibility(View.VISIBLE);
                mButton.setText(style.titleRes);
                mButton.setCompoundDrawablesWithIntrinsicBounds(getResources()
                        .getDrawable(style.iconRes), null, null, null);
            } else {
                mButton.setVisibility(View.GONE);
                findViewById(R.id.undobar_divider).setVisibility(View.GONE);
            }
            findViewById(R.id._undobar).setBackgroundResource(style.bgRes);
        }

        mHideHandler.removeCallbacks(mHideRunnable);
        if (style.duration > 0) {
            mHideHandler.postDelayed(mHideRunnable, style.duration);
        }
        if (!immediate) {
            clearAnimation();
            startAnimation(UndoBarController.inFromBottomAnimation(null));
        }
        setVisibility(View.VISIBLE);
    }

    private static Animation inFromBottomAnimation(
            final android.view.animation.Animation.AnimationListener animationlistener) {
        final TranslateAnimation translateanimation = new TranslateAnimation(2,
                0F, 2, 0F, 2, 1F, 2, 0F);
        translateanimation.setDuration(500L);
        translateanimation.setInterpolator(new OvershootInterpolator(1.0f));
        translateanimation.setAnimationListener(animationlistener);
        return translateanimation;
    }

    /**
     * Quick method to insert a UndoBar into an Activity
     *
     * @param activity  Activity to hold this view
     * @param message   The message will be shown in left side in undobar
     * @param listener  Callback listener triggered after click undobar
     * @param undoToken Token info,will pass to callback to help you to undo
     * @param immediate Show undobar immediately or show it with animation
     * @param style     {@link UndoBarStyle}
     * @return
     */
    public static UndoBarController show(final Activity activity,
                                         final CharSequence message, final UndoListener listener,
                                         final Parcelable undoToken, final boolean immediate,
                                         final UndoBarStyle style) {
        UndoBarController undo = UndoBarController.getView(activity);
        if (undo == null) {
            undo = new UndoBarController(activity, null);
            ((ViewGroup) activity.findViewById(android.R.id.content))
                    .addView(undo);
        }
        undo.style = style;
        undo.setUndoListener(listener);
        undo.showUndoBar(immediate, message, undoToken);
        return undo;
    }

    private static UndoBarController getView(final Activity activity) {
        final View view = activity.findViewById(R.id._undobar);
        UndoBarController undo = null;
        if (view != null) {
            undo = (UndoBarController) view.getParent();
        }
        return undo;
    }

    public static UndoBarController show(final Activity activity,
                                         final int message, final UndoListener listener,
                                         final Parcelable undoToken, final boolean immediate) {
        return UndoBarController.show(activity, activity.getText(message),
                listener, undoToken, immediate, UndoBarController.UNDOSTYLE);
    }

    public static UndoBarController show(final Activity activity,
                                         final CharSequence message, final UndoListener listener,
                                         final Parcelable undoToken) {
        return UndoBarController.show(activity, message, listener, undoToken,
                false, UndoBarController.UNDOSTYLE);
    }

    public static UndoBarController show(final Activity activity,
                                         final CharSequence message, final UndoListener listener,
                                         final UndoBarStyle style) {
        return UndoBarController.show(activity, message, listener, null, false,
                style);
    }

    public static UndoBarController show(final Activity activity,
                                         final CharSequence message, final UndoListener listener) {
        return UndoBarController.show(activity, message, listener, null, false,
                UndoBarController.UNDOSTYLE);
    }

    public static UndoBarController show(final Activity activity,
                                         final CharSequence message) {
        return UndoBarController.show(activity, message, null, null, false,
                UndoBarController.MESSAGESTYLE);
    }

    /**
     * hide all undo bar immediately
     *
     * @param activity
     */
    public static void clear(final Activity activity) {
        final UndoBarController v = UndoBarController.getView(activity);
        if (v != null) {
            v.setVisibility(View.GONE);
        }
    }
}
src/main/java/org/centum/android/HelpActivity.java
public class HelpActivity extends Activity {

    private WebView webView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.help_activity);
        webView = (WebView) findViewById(R.id.webview);

        if (getIntent().hasExtra("page")) {
            webView.loadUrl(getIntent().getStringExtra("page"));
        } else {
            webView.loadUrl("file:///android_asset/index.html");
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.help, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                onBackPressed();
                return true;
            case R.id.action_back:
                if (webView.canGoBack()) {
                    webView.goBack();
                }
                return true;
            case R.id.action_forward:
                if (webView.canGoForward()) {
                    webView.goForward();
                }
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onBackPressed() {
        if (webView.canGoBack()) {
            webView.goBack();
        } else {
            super.onBackPressed();
        }
    }

}
src/main/java/com/google/android/apps/dashclock/ui/SwipeDismissGridViewTouchListener.java
public class SwipeDismissGridViewTouchListener implements View.OnTouchListener {
    // Cached ViewConfiguration and system-wide constant values
    private int mSlop;
    private int mMinFlingVelocity;
    private int mMaxFlingVelocity;
    private long mAnimationTime;

    // Fixed properties
    private GridView mGridView;
    private DismissCallbacks mCallbacks;
    private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero

    // Transient properties
    private List<PendingDismissData> mPendingDismisses = new ArrayList<PendingDismissData>();
    private int mDismissAnimationRefCount = 0;
    private float mDownX;
    private boolean mSwiping;
    private VelocityTracker mVelocityTracker;
    private int mDownPosition;
    private View mDownView;
    private boolean mPaused;

    /**
     * The callback interface used by {@link SwipeDismissListViewTouchListener} to inform its client
     * about a successful dismissal of one or more list item positions.
     */
    public interface DismissCallbacks {
        /**
         * Called to determine whether the given position can be dismissed.
         */
        boolean canDismiss(int position);

        /**
         * Called when the user has indicated they she would like to dismiss one or more list item
         * positions.
         *
         * @param gridView               The originating {@link ListView}.
         * @param reverseSortedPositions An array of positions to dismiss, sorted in descending
         *                               order for convenience.
         */
        void onDismiss(GridView gridView, int[] reverseSortedPositions);
    }

    /**
     * Constructs a new swipe-to-dismiss touch listener for the given list view.
     *
     * @param listView  The list view whose items should be dismissable.
     * @param callbacks The callback to trigger when the user has indicated that she would like to
     *                  dismiss one or more list items.
     */
    public SwipeDismissGridViewTouchListener(GridView listView, DismissCallbacks callbacks) {
        ViewConfiguration vc = ViewConfiguration.get(listView.getContext());
        mSlop = vc.getScaledTouchSlop();
        mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 16;
        mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
        mAnimationTime = listView.getContext().getResources().getInteger(
                android.R.integer.config_shortAnimTime);
        mGridView = listView;
        mCallbacks = callbacks;
    }

    /**
     * Enables or disables (pauses or resumes) watching for swipe-to-dismiss gestures.
     *
     * @param enabled Whether or not to watch for gestures.
     */
    public void setEnabled(boolean enabled) {
        mPaused = !enabled;
    }

    /**
     * Returns an {@link android.widget.AbsListView.OnScrollListener} to be added to the {@link
     * ListView} using {@link ListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener)}.
     * If a scroll listener is already assigned, the caller should still pass scroll changes through
     * to this listener. This will ensure that this {@link SwipeDismissListViewTouchListener} is
     * paused during list view scrolling.</p>
     *
     * @see SwipeDismissListViewTouchListener
     */
    public AbsListView.OnScrollListener makeScrollListener() {
        return new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView absListView, int scrollState) {
                setEnabled(scrollState != AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
            }

            @Override
            public void onScroll(AbsListView absListView, int i, int i1, int i2) {
            }
        };
    }

    /**
     * Manually cause the item at the given position to be dismissed (trigger the dismiss
     * animation).
     */
    public void dismiss(int position) {
        dismiss(getViewForPosition(position), position, true);
    }

    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        if (mViewWidth < 2) {
            mViewWidth = mGridView.getWidth() / mGridView.getNumColumns();
        }

        switch (motionEvent.getActionMasked()) {
            case MotionEvent.ACTION_DOWN: {
                if (mPaused) {
                    return false;
                }

                // Find the child view that was touched (perform a hit test)
                Rect rect = new Rect();
                int childCount = mGridView.getChildCount();
                int[] listViewCoords = new int[2];
                mGridView.getLocationOnScreen(listViewCoords);
                int x = (int) motionEvent.getRawX() - listViewCoords[0];
                int y = (int) motionEvent.getRawY() - listViewCoords[1];
                View child;
                for (int i = 0; i < childCount; i++) {
                    child = mGridView.getChildAt(i);
                    child.getHitRect(rect);
                    if (rect.contains(x, y)) {
                        mDownView = child;
                        break;
                    }
                }

                if (mDownView != null) {
                    mDownX = motionEvent.getRawX();
                    mDownPosition = mGridView.getPositionForView(mDownView);
                    if (mCallbacks.canDismiss(mDownPosition)) {
                        mVelocityTracker = VelocityTracker.obtain();
                        mVelocityTracker.addMovement(motionEvent);
                    } else {
                        mDownView = null;
                    }
                }
                view.onTouchEvent(motionEvent);
                return true;
            }

            case MotionEvent.ACTION_UP: {
                if (mVelocityTracker == null) {
                    break;
                }

                float deltaX = motionEvent.getRawX() - mDownX;
                mVelocityTracker.addMovement(motionEvent);
                mVelocityTracker.computeCurrentVelocity(1000);
                float velocityX = mVelocityTracker.getXVelocity();
                float absVelocityX = Math.abs(velocityX);
                float absVelocityY = Math.abs(mVelocityTracker.getYVelocity());
                boolean dismiss = false;
                boolean dismissRight = false;
                if (Math.abs(deltaX) > mViewWidth / 2) {
                    dismiss = true;
                    dismissRight = deltaX > 0;
                } else if (mMinFlingVelocity <= absVelocityX && absVelocityX <= mMaxFlingVelocity
                        && absVelocityY < absVelocityX) {
                    // dismiss only if flinging in the same direction as dragging
                    dismiss = (velocityX < 0) == (deltaX < 0);
                    dismissRight = mVelocityTracker.getXVelocity() > 0;
                }
                if (dismiss) {
                    // dismiss
                    dismiss(mDownView, mDownPosition, dismissRight);
                } else {
                    // cancel
                    mDownView.animate()
                            .translationX(0)
                            .alpha(1)
                            .setDuration(mAnimationTime)
                            .setListener(null);
                }
                mVelocityTracker.recycle();
                mVelocityTracker = null;
                mDownX = 0;
                mDownView = null;
                mDownPosition = ListView.INVALID_POSITION;
                mSwiping = false;
                break;
            }

            case MotionEvent.ACTION_CANCEL: {
                if (mVelocityTracker == null) {
                    break;
                }

                if (mDownView != null) {
                    // cancel
                    mDownView.animate()
                            .translationX(0)
                            .alpha(1)
                            .setDuration(mAnimationTime)
                            .setListener(null);
                }
                mVelocityTracker.recycle();
                mVelocityTracker = null;
                mDownX = 0;
                mDownView = null;
                mDownPosition = ListView.INVALID_POSITION;
                mSwiping = false;
                break;
            }

            case MotionEvent.ACTION_MOVE: {
                if (mVelocityTracker == null || mPaused) {
                    break;
                }

                mVelocityTracker.addMovement(motionEvent);
                float deltaX = motionEvent.getRawX() - mDownX;
                if (Math.abs(deltaX) > mSlop) {
                    mSwiping = true;
                    mGridView.requestDisallowInterceptTouchEvent(true);

                    // Cancel ListView's touch (un-highlighting the item)
                    MotionEvent cancelEvent = MotionEvent.obtain(motionEvent);
                    cancelEvent.setAction(MotionEvent.ACTION_CANCEL |
                            (motionEvent.getActionIndex()
                                    << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
                    mGridView.onTouchEvent(cancelEvent);
                    cancelEvent.recycle();
                }

                if (mSwiping) {
                    mDownView.setTranslationX(deltaX);
                    mDownView.setAlpha(Math.max(0.15f, Math.min(1f,
                            1f - 2f * Math.abs(deltaX) / mViewWidth)));
                    return true;
                }
                break;
            }
        }
        return false;
    }

    private void dismiss(final View view, final int position, boolean dismissRight) {
        ++mDismissAnimationRefCount;
        if (view == null) {
            // No view, shortcut to calling onDismiss to let it deal with adapter
            // updates and all that.
            mCallbacks.onDismiss(mGridView, new int[]{position});
            return;
        }

        view.animate()
                .translationX(dismissRight ? mViewWidth : -mViewWidth)
                .alpha(0)
                .setDuration(mAnimationTime)
                .setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        performDismiss(view, position);
                    }
                });
    }

    private View getViewForPosition(int position) {
        int index = position
                - (mGridView.getFirstVisiblePosition());
        return (index >= 0 && index < mGridView.getChildCount())
                ? mGridView.getChildAt(index)
                : null;
    }

    class PendingDismissData implements Comparable<PendingDismissData> {
        public int position;
        public View view;

        public PendingDismissData(int position, View view) {
            this.position = position;
            this.view = view;
        }

        @Override
        public int compareTo(PendingDismissData other) {
            // Sort by descending position
            return other.position - position;
        }
    }

    private void performDismiss(final View dismissView, final int dismissPosition) {
        // Animate the dismissed list item to zero-height and fire the dismiss callback when
        // all dismissed list item animations have completed. This triggers layout on each animation
        // frame; in the future we may want to do something smarter and more performant.

        final ViewGroup.LayoutParams lp = dismissView.getLayoutParams();
        final int originalHeight = dismissView.getHeight();

        ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime);

        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                --mDismissAnimationRefCount;
                if (mDismissAnimationRefCount == 0) {
                    // No active animations, process all pending dismisses.
                    // Sort by descending position
                    Collections.sort(mPendingDismisses);

                    int[] dismissPositions = new int[mPendingDismisses.size()];
                    for (int i = mPendingDismisses.size() - 1; i >= 0; i--) {
                        dismissPositions[i] = mPendingDismisses.get(i).position;
                    }
                    mCallbacks.onDismiss(mGridView, dismissPositions);

                    ViewGroup.LayoutParams lp;
                    for (PendingDismissData pendingDismiss : mPendingDismisses) {
                        // Reset view presentation
                        pendingDismiss.view.setAlpha(1f);
                        pendingDismiss.view.setTranslationX(0);
                        lp = pendingDismiss.view.getLayoutParams();
                        lp.height = originalHeight;
                        pendingDismiss.view.setLayoutParams(lp);
                    }

                    mPendingDismisses.clear();
                }
            }
        });

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                lp.height = (Integer) valueAnimator.getAnimatedValue();
                dismissView.setLayoutParams(lp);
            }
        });

        mPendingDismisses.add(new PendingDismissData(dismissPosition, dismissView));
        animator.start();
    }
}
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);
    }
}


Question: import android.app.AlertDialog;
import android.app.Fragment;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.SearchView;
import android.widget.TextView;
import android.widget.Toast;
import com.cocosw.undobar.UndoBarController;
import com.google.android.apps.dashclock.ui.SwipeDismissGridViewTouchListener;
import org.centum.android.HelpActivity;
import org.centum.android.model.Card;
import org.centum.android.model.Stack;
import org.centum.android.model.StackManager;
import org.centum.android.model.events.StackEvent;
import org.centum.android.model.events.StackListener;
import org.centum.android.presentation.general.PlaySessionSettingsDialogFragment;
import org.centum.android.presentation.learn.LearnActivity;
import org.centum.android.presentation.mixed.MixedActivity;
import org.centum.android.settings.SettingsActivity;
import org.centum.android.sql.StacksDatabaseHelper;
import org.centum.android.stack.R;
import org.centum.android.stats.StatsActivity;
import org.centum.android.utils.Clipboard;
import java.util.ArrayList;
import java.util.List;
        emptyTextView.setVisibility(b ? View.VISIBLE : View.GONE);
        buttonBarLayout.setVisibility(b ? View.GONE : View.VISIBLE);
    }

    public Stack getStack() {
        return stack;
    }

    public void setStack(final Stack stack) {
        if (this.stack != null) {
            this.stack.removeListener(this);
        }

        if (this.stack != stack) {
            this.stack = stack;

            if (stack != null) {
                quizletLink.setVisibility(View.GONE);
                cardListAdapter = new CardListAdapter(this, stack);
                cardList.setAdapter(cardListAdapter);
                touchListener = new SwipeDismissGridViewTouchListener(cardList, this);
                cardList.setOnTouchListener(touchListener);
                cardList.setOnScrollListener(touchListener.makeScrollListener());
                if (stack.getNumberOfCards() > 100) {
                    cardList.setFastScrollEnabled(true);
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                        cardList.setFastScrollAlwaysVisible(true);
                    }
                } else {
                    cardList.setFastScrollEnabled(false);
                    cardList.setFastScrollAlwaysVisible(false);
                }
                stack.addListener(this);
            } else {
                cardList.setAdapter(null);
                quizletLink.setVisibility(View.GONE);
            }
        }
        StackManager.get().setCurrentStack(stack);
        updateEmptyViews();

    }

    @Override
    public boolean canDismiss(int position) {
        return actionMode == null;
    }

    public void onDismiss(GridView gridView, int[] reverseSortedPositions) {
        final List<Card> removedCards = new ArrayList<Card>();

        deleteOnSwipe = PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean(SettingsActivity.KEY_PREF_CARD_SWIPE_DELETES, false);

        for (int position : reverseSortedPositions) {
            if (position < stack.getNumberOfCards()) {
                removedCards.add(stack.getCard(position));
            }
        }

        for (Card card : removedCards) {
            if (deleteOnSwipe) {
                stack.removeCard(card, false);
            } else {
                stack.removeCard(card);
            }
        }

        if (getActivity() != null) {
            UndoBarController.show(getActivity(), deleteOnSwipe ? "Card deleted." : "Card archived.", new UndoBarController.UndoListener() {
                @Override
                public void onUndo(Parcelable token) {
                    for (Card card : removedCards) {
                        if (deleteOnSwipe) {
                            stack.addCard(card);
                        } else {
                            stack.restoreArchivedCard(card);
                        }
                    }
                }
            });
        }
    }

    private void showQuizlet() {
        String _url = "http://quizlet.com/";
        Intent webIntent = new Intent(Intent.ACTION_VIEW);
        webIntent.setData(Uri.parse(_url));
        startActivity(webIntent);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.learn_button:
                startLearnActivity();
                break;
            case R.id.test_button:
                onTestActionPerformed();
                break;
        }
    }

    private void onTestActionPerformed() {
        if (stack != null) {
            if (stack.getNumberOfCards() < 4) {
                new AlertDialog.Builder(getActivity()).setTitle("Oops!")
                        .setMessage("You need at least 4 cards for this").show();
            } else {
                final PlaySessionSettingsDialogFragment dialogFragment = new PlaySessionSettingsDialogFragment();
                dialogFragment.setStack(stack);
                dialogFragment.setOnClickListener(new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                        if (dialogFragment.getPlaySession() != null) {
                            stack.addPlaySession(dialogFragment.getPlaySession());
                            if (PreferenceManager.getDefaultSharedPreferences(getActivity())
                                    .getBoolean(SettingsActivity.KEY_PREF_PLAY_SHUFFLE_TEST, true)) {
                                stack.shuffle();
                            }
Answer:                             Intent intent = new Intent(getActivity(), MixedActivity.class);