com.gelakinetic.mtgfam.fragments.LifeCounterFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.gelakinetic.mtgfam.fragments.LifeCounterFragment.java

Source

/*
 * Copyright 2017 Adam Feinstein
 *
 * This file is part of MTG Familiar.
 *
 * MTG Familiar is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * MTG Familiar is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with MTG Familiar.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.gelakinetic.mtgfam.fragments;

import android.content.Context;
import android.content.res.Configuration;
import android.graphics.PorterDuff;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.support.v4.content.ContextCompat;
import android.util.TypedValue;
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.view.WindowManager;
import android.widget.GridLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;

import com.gelakinetic.mtgfam.FamiliarActivity;
import com.gelakinetic.mtgfam.R;
import com.gelakinetic.mtgfam.fragments.dialogs.FamiliarDialogFragment;
import com.gelakinetic.mtgfam.fragments.dialogs.LifeCounterDialogFragment;
import com.gelakinetic.mtgfam.helpers.LcPlayer;
import com.gelakinetic.mtgfam.helpers.LcPlayer.CommanderEntry;
import com.gelakinetic.mtgfam.helpers.LcPlayer.HistoryEntry;
import com.gelakinetic.mtgfam.helpers.PreferenceAdapter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;

public class LifeCounterFragment extends FamiliarFragment implements TextToSpeech.OnInitListener,
        AudioManager.OnAudioFocusChangeListener, TextToSpeech.OnUtteranceCompletedListener {

    /* constants for display mode */
    public static final int DISPLAY_NORMAL = 0;
    public static final int DISPLAY_COMPACT = 1;
    public static final int DISPLAY_COMMANDER = 2;
    /* constants for stat displaying */
    public final static int STAT_LIFE = 0;
    public final static int STAT_POISON = 1;
    public final static int STAT_COMMANDER = 2;
    /* Life total constants */
    public static final int DEFAULT_LIFE_COMMANDER = 40;
    public static final int DEFAULT_LIFE = 20;
    /* Constant for persisting data */
    private static final String DISPLAY_MODE = "display_mode";
    /* Constants for TTS */
    private static final String LIFE_ANNOUNCE = "life_announce";
    private static final int IMPROBABLE_NUMBER = 531865548;
    private static final String OVER_9000_KEY = "@over_9000";
    /* Keeping track of players, display state */
    public final ArrayList<LcPlayer> mPlayers = new ArrayList<>();
    private final LinkedList<String> mVocalizations = new LinkedList<>();
    public int mDisplayMode = DISPLAY_NORMAL;
    private int mStatDisplaying = STAT_LIFE;
    /* UI Elements, measurement */
    public GridLayout mGridLayout;
    public LinearLayout mCommanderPlayerView;
    private ImageView mPoisonButton;
    private ImageView mLifeButton;
    private ImageView mCommanderButton;
    private View mScrollView;
    private int mListSizeWidth = -1;
    private int mListSizeHeight = -1;
    public int mLargestPlayerNumber = 0;
    /* TTS variables */
    private TextToSpeech mTts;
    private boolean mTtsInit;
    private AudioManager mAudioManager;
    private MediaPlayer m9000Player;

    /**
     * When the fragment is created, set up the TTS engine, AudioManager, and MediaPlayer for life total vocalization
     *
     * @param savedInstanceState If the fragment is being re-created from a previous saved state, this is the state.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mTtsInit = false;
        mTts = new TextToSpeech(getActivity(), this);
        mTts.setOnUtteranceCompletedListener(this);

        mAudioManager = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);

        m9000Player = MediaPlayer.create(getActivity(), R.raw.over_9000);
        if (m9000Player != null) {
            m9000Player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                public void onCompletion(MediaPlayer mp) {
                    onUtteranceCompleted(LIFE_ANNOUNCE);
                }
            });
        }
    }

    /**
     * When the fragment is destroyed, clean up the TTS engine and MediaPlayer
     */
    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mTts != null) {
            mTts.stop();
            mTts.shutdown();
        }
        if (m9000Player != null) {
            m9000Player.reset();
            m9000Player.release();
        }
    }

    /**
     * Get UI element references, set onClickListeners for the toolbar, clear the measurement data and attach a
     * ViewTreeObserver to measure the UI when it is drawn. Get the life/poison mode from the savedInstanceState if the
     * fragment is persisting. Save the current brightness. Players are not added here.
     *
     * @param inflater           The LayoutInflater object that can be used to inflate any views in the fragment,
     * @param container          If non-null, this is the parent view that the fragment's UI should be attached to. The
     *                           fragment should not add the view itself, but this can be used to generate the
     *                           LayoutParams of the view.
     * @param savedInstanceState If non-null, this fragment is being re-constructed from a previous saved state as given
     *                           here.
     * @return The inflated view
     */
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        mListSizeWidth = -1;
        mListSizeHeight = -1;

        View myFragmentView = inflater.inflate(R.layout.life_counter_frag, container, false);
        assert myFragmentView != null;
        mGridLayout = myFragmentView.findViewById(R.id.playerList);

        mDisplayMode = Integer.parseInt(PreferenceAdapter.getDisplayMode(getContext()));

        mCommanderPlayerView = myFragmentView.findViewById(R.id.commander_player);

        if (null != myFragmentView.findViewById(R.id.playerScrollView_horz)) {
            mScrollView = myFragmentView.findViewById(R.id.playerScrollView_horz);
        } else {
            mScrollView = myFragmentView.findViewById(R.id.playerScrollView_vert);
        }
        ViewTreeObserver viewTreeObserver = mScrollView.getViewTreeObserver();
        assert viewTreeObserver != null;
        viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            public void onGlobalLayout() {
                if (isVisible()) {
                    boolean changed = false;
                    if (mListSizeHeight < mScrollView.getHeight()) {
                        mListSizeHeight = mScrollView.getHeight();
                        changed = true;
                    }
                    if (mListSizeWidth < mScrollView.getWidth()) {
                        mListSizeWidth = mScrollView.getWidth();
                        changed = true;
                    }
                    if (changed) {
                        if (getActivity().getResources()
                                .getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
                            if (mDisplayMode == DISPLAY_COMMANDER) {
                                /* Conveniently takes care of re-adding the sized views in the right number of rows */
                                changeDisplayMode(false);
                            }
                        }
                        for (LcPlayer player : mPlayers) {
                            player.setSize(mListSizeWidth, mListSizeHeight, mDisplayMode,
                                    getActivity().getResources()
                                            .getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT);
                        }
                    }
                }
            }
        });

        mPoisonButton = myFragmentView.findViewById(R.id.poison_button);
        mPoisonButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
                setStatDisplaying(STAT_POISON);
            }
        });

        mLifeButton = myFragmentView.findViewById(R.id.life_button);
        mLifeButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
                setStatDisplaying(STAT_LIFE);
            }
        });

        mCommanderButton = myFragmentView.findViewById(R.id.commander_button);
        mCommanderButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
                setStatDisplaying(STAT_COMMANDER);
            }
        });

        myFragmentView.findViewById(R.id.reset_button).setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
                showDialog(LifeCounterDialogFragment.DIALOG_RESET_CONFIRM);
            }
        });

        if (savedInstanceState != null) {
            mStatDisplaying = savedInstanceState.getInt(DISPLAY_MODE, STAT_LIFE);
        }

        return myFragmentView;
    }

    /**
     * When the orientation is changed, save mStatDisplaying so that the fragment can display the right thing
     * when it is recreated
     *
     * @param outState Bundle in which to place your saved state.
     */
    @Override
    public void onSaveInstanceState(Bundle outState) {
        outState.putInt(DISPLAY_MODE, mStatDisplaying);
        super.onSaveInstanceState(outState);
    }

    /**
     * When the fragment is paused, save all the player data to shared preferences, in string form, then remove all
     * the player views from the ListView and clear the players ArrayList.
     */
    @Override
    public void onPause() {
        super.onPause();
        StringBuilder playerData = new StringBuilder();
        for (LcPlayer player : mPlayers) {
            player.onPause();
            playerData.append(player.toString());
        }
        PreferenceAdapter.setPlayerData(getContext(), playerData.toString());
        mGridLayout.removeAllViews();
        mPlayers.clear();

        /* Remove the screen on lock, restore the brightness */
        getActivity().getWindow().clearFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        onUserActive();
    }

    /**
     * When the fragment is resumed, attempt to populate the life counter with player information in shared preferences.
     * If that doesn't exist, add the two default players. Set whether to display life or poison based on persisted
     * data. Add commander data to each player, and set the current display mode and stat displaying, set in
     * onCreateView. addPlayer() adds players to the ArrayList, and setStatDisplaying() takes care of drawing the player
     * Views
     */
    @Override
    public void onResume() {
        super.onResume();

        String playerData = PreferenceAdapter.getPlayerData(getContext());
        if (playerData == null || playerData.length() == 0) {
            addPlayer();
            addPlayer();
        } else {
            String[] playerLines = playerData.split("\n");
            for (String line : playerLines) {
                addPlayer(line);
            }
        }

        setCommanderInfo(-1);

        changeDisplayMode(false);

        setStatDisplaying(mStatDisplaying);

        if (PreferenceAdapter.getKeepScreenOn(getContext())) {
            getActivity().getWindow().addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        }
    }

    /**
     * Called from the activity if the user is inactive. Dims the screen.
     */
    @Override
    public void onUserInactive() {
        try {
            if (PreferenceAdapter.getKeepScreenOn(getContext()) && PreferenceAdapter.getDimScreen(getContext())) {
                float dimLevel = (float) PreferenceAdapter.getDimLevel(getContext()) / (float) 100;
                WindowManager.LayoutParams layoutParams = getActivity().getWindow().getAttributes();
                layoutParams.screenBrightness = dimLevel;
                getActivity().getWindow().setAttributes(layoutParams);
            }
        } catch (NullPointerException e) {
            /* Can't dim the screen, oh well */
        }
    }

    /**
     * Called from the activity if the user is active again. Restore the saved brightness.
     */
    @Override
    public void onUserActive() {
        if (PreferenceAdapter.getKeepScreenOn(getContext())) {
            WindowManager.LayoutParams layoutParams = getActivity().getWindow().getAttributes();
            layoutParams.screenBrightness = -1;
            getActivity().getWindow().setAttributes(layoutParams);
        }
    }

    /**
     * Inflate the options menu.
     *
     * @param menu     The options menu in which you place your items.
     * @param inflater The inflater to use to inflate the menu
     */
    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);
        inflater.inflate(R.menu.life_counter_menu, menu);
    }

    /**
     * If TTS is not initialized, remove it from the menu. If it is initialized, show it.
     *
     * @param menu The menu to show or hide the "announce life totals" button in.
     */
    @Override
    public void onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);
        MenuItem menuItem = menu.findItem(R.id.announce_life);
        assert menuItem != null;
        if (!mTtsInit || getFamiliarActivity() == null || !getFamiliarActivity().mIsMenuVisible) {
            menuItem.setVisible(false);
        } else {
            menuItem.setVisible(true);
        }
    }

    /**
     * Handle menu items being selected
     *
     * @param item The menu item selected
     * @return true if the selection was acted upon, false otherwise
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        /* Handle item selection */
        switch (item.getItemId()) {
        case R.id.add_player:
            /* Add the player to the ArrayList, set the commander info, and draw the new view */
            addPlayer();
            setCommanderInfo(-1);
            addPlayerView(mPlayers.get(mPlayers.size() - 1));
            return true;
        case R.id.remove_player:
            /* Show a dialog of players to remove */
            showDialog(LifeCounterDialogFragment.DIALOG_REMOVE_PLAYER);
            return true;
        case R.id.announce_life:
            /* Vocalize the current life totals */
            announceLifeTotals();
            return true;
        case R.id.edit_gatherings:
            /* Start a GatheringsFragment to edit gatherings */
            GatheringsFragment rlFrag = new GatheringsFragment();
            startNewFragment(rlFrag, null);
            return true;
        case R.id.set_gathering:
            /* Show a dialog of gatherings a user can set */
            showDialog(LifeCounterDialogFragment.DIALOG_SET_GATHERING);
            return true;
        case R.id.display_mode:
            /* Show a dialog to change the display mode (normal, compact, commander) */
            showDialog(LifeCounterDialogFragment.DIALOG_CHANGE_DISPLAY);
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }

    /**
     * Remove any showing dialogs, and show the requested one
     *
     * @param id the ID of the dialog to show
     */
    private void showDialog(final int id) throws IllegalStateException {
        /* DialogFragment.show() will take care of adding the fragment in a transaction. We also want to remove any
        currently showing dialog, so make our own transaction and take care of that here. */

        /* If the fragment isn't visible (maybe being loaded by the pager), don't show dialogs */
        if (!this.isVisible()) {
            return;
        }

        removeDialog(getFragmentManager());

        /* Create and show the dialog. */
        LifeCounterDialogFragment newFragment = new LifeCounterDialogFragment();
        Bundle arguments = new Bundle();
        arguments.putInt(FamiliarDialogFragment.ID_KEY, id);
        newFragment.setArguments(arguments);
        newFragment.show(getFragmentManager(), FamiliarActivity.DIALOG_TAG);
    }

    /**
     * Update the list of who a player took commander damage from. This info doesn't exist in saved player data, hence
     * this method. If an entry in the player's list of opponents exists, update the name. Otherwise, add it to the
     * list. If toBeRemoved is positive, remove that opponent from each player's list instead of adding/updating.
     *
     * @param toBeRemoved The index of the opponent to be removed, or negative if an opponent is being added/updated
     */
    public void setCommanderInfo(int toBeRemoved) {
        for (LcPlayer player1 : mPlayers) {
            if (toBeRemoved != -1) {
                player1.mCommanderDamage.remove(toBeRemoved);
            } else {
                for (int i = 0; i < mPlayers.size(); i++) {
                    /* An entry for this player exists, just set the name */
                    if (player1.mCommanderDamage.size() > i) {
                        player1.mCommanderDamage.get(i).mName = mPlayers.get(i).mName;
                    }
                    /* An entry for this player doesn't exist, create one and add it */
                    else {
                        CommanderEntry ce = new CommanderEntry();
                        ce.mName = mPlayers.get(i).mName;
                        ce.mLife = 0;
                        player1.mCommanderDamage.add(ce);
                    }
                }
            }
            /* Redraw the information */
            if (player1.mCommanderDamageAdapter != null) {
                player1.mCommanderDamageAdapter.notifyDataSetChanged();
            }
        }
    }

    /**
     * Updates the display mode based on the current value of mDisplayMode. This updates the GridLayout's parameters
     * and draws the player's views in the fragment. It also shows and hides buttons and views relating to
     * commander mode.
     */
    public void changeDisplayMode(boolean shouldDefaultLives) {
        /* update the preference */
        PreferenceAdapter.setDisplayMode(getContext(), String.valueOf(mDisplayMode));

        mGridLayout.removeAllViews();

        if (getActivity().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
            switch (mDisplayMode) {
            case DISPLAY_NORMAL:
                mGridLayout.setOrientation(GridLayout.HORIZONTAL);
                mGridLayout.setColumnCount(1);
                mGridLayout.setRowCount(GridLayout.UNDEFINED);
                break;
            case DISPLAY_COMPACT:
                mGridLayout.setOrientation(GridLayout.HORIZONTAL);
                mGridLayout.setColumnCount(2);
                mGridLayout.setRowCount(GridLayout.UNDEFINED);
                break;
            case DISPLAY_COMMANDER:
                mGridLayout.setOrientation(GridLayout.HORIZONTAL);
                mGridLayout.setColumnCount(2);
                mGridLayout.setRowCount(GridLayout.UNDEFINED);
                break;
            }
        } else {
            switch (mDisplayMode) {
            case DISPLAY_NORMAL:
                mGridLayout.setOrientation(GridLayout.HORIZONTAL);
                mGridLayout.setColumnCount(GridLayout.UNDEFINED);
                mGridLayout.setRowCount(1);
                break;
            case DISPLAY_COMPACT:
                mGridLayout.setOrientation(GridLayout.HORIZONTAL);
                mGridLayout.setColumnCount(GridLayout.UNDEFINED);
                mGridLayout.setRowCount(1);
                break;
            case DISPLAY_COMMANDER:
                mGridLayout.setOrientation(GridLayout.VERTICAL);
                mGridLayout.setColumnCount(GridLayout.UNDEFINED);
                if (mListSizeHeight != -1) {
                    float height = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
                            getActivity().getResources().getDisplayMetrics());
                    mGridLayout.setRowCount((int) (mListSizeHeight / height));
                } else {
                    mGridLayout.setRowCount(GridLayout.UNDEFINED);
                }
                break;
            }
        }

        boolean areLivesDefault = true;
        for (LcPlayer player : mPlayers) {
            /* Only reset a player's default life / life if that player is unaltered and doesn't have a noticeably
             * custom default life */
            if (!(player.mLifeHistory.size() == 0 && player.mPoisonHistory.size() == 0
                    && player.mLife == player.mDefaultLifeTotal && (player.mDefaultLifeTotal == DEFAULT_LIFE
                            || player.mDefaultLifeTotal == DEFAULT_LIFE_COMMANDER))) {
                areLivesDefault = false;
            }
        }

        if (areLivesDefault && shouldDefaultLives) {
            for (LcPlayer player : mPlayers) {
                player.mDefaultLifeTotal = getDefaultLife();
                player.mLife = player.mDefaultLifeTotal;
            }
        }

        for (LcPlayer player : mPlayers) {
            /* Draw the player's view */
            addPlayerView(player);
        }

        if (mDisplayMode == DISPLAY_COMMANDER) {
            mCommanderButton.setVisibility(View.VISIBLE);
            mCommanderPlayerView.setVisibility(View.VISIBLE);
            mCommanderPlayerView.removeAllViews();
            if (mPlayers.size() > 0 && null != mPlayers.get(0).mView) {
                mCommanderPlayerView.addView(mPlayers.get(0).mView);
                mPlayers.get(0).setSize(mListSizeWidth, mListSizeHeight, mDisplayMode, getActivity().getResources()
                        .getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT);
            }
        } else {
            mCommanderPlayerView.setVisibility(View.GONE);
            mCommanderButton.setVisibility(View.GONE);
            if (mStatDisplaying == STAT_COMMANDER) {
                setStatDisplaying(STAT_LIFE);
            }
        }
    }

    /**
     * This function adds the player's view to the GridLayout. player.newView() will inflate the view. If the counter
     * is in commander mode, an onClickListener will be set to display that player's information in
     * mCommanderPlayerView.
     *
     * @param player The player to be added
     */
    private void addPlayerView(final LcPlayer player) {
        try {
            mGridLayout.addView(player.newView(mDisplayMode, mStatDisplaying, mGridLayout, mCommanderPlayerView));
        } catch (IllegalArgumentException e) {
            return;
        }
        if (mDisplayMode == DISPLAY_COMMANDER) {
            player.mCommanderRowView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    /* Show this player's info in mCommanderPlayerView */
                    mCommanderPlayerView.removeAllViews();
                    mCommanderPlayerView.addView(player.mView);
                    player.setSize(mListSizeWidth, mListSizeHeight, mDisplayMode, getActivity().getResources()
                            .getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT);
                }
            });

            if (mPlayers.size() == 1) {
                mCommanderPlayerView.addView(mPlayers.get(0).mView);
            }
        }

        /* If the size has already been measured, set the player's size */
        if (mListSizeHeight != -1) {
            player.setSize(mListSizeWidth, mListSizeHeight, mDisplayMode, getActivity().getResources()
                    .getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT);
        }
    }

    /**
     * Change whether life, poison, or commander damage is displayed. This will redraw buttons and notify the LcPlayer
     * objects to show different things.
     *
     * @param statMode either STAT_LIFE, STAT_POISON, or STAT_COMMANDER
     */
    private void setStatDisplaying(int statMode) {
        mStatDisplaying = statMode;

        int disabledColor = ContextCompat.getColor(getContext(),
                getFamiliarActivity().getResourceIdFromAttr(R.attr.lc_disabled));
        int enabledColor = ContextCompat.getColor(getContext(),
                getFamiliarActivity().getResourceIdFromAttr(R.attr.lc_enabled));

        // Disable all buttons
        mLifeButton.getDrawable().setColorFilter(disabledColor, PorterDuff.Mode.SRC_IN);
        mPoisonButton.getDrawable().setColorFilter(disabledColor, PorterDuff.Mode.SRC_IN);
        mCommanderButton.getDrawable().setColorFilter(disabledColor, PorterDuff.Mode.SRC_IN);

        // Enable the selected one
        switch (statMode) {
        case STAT_LIFE:
            mLifeButton.getDrawable().setColorFilter(enabledColor, PorterDuff.Mode.SRC_IN);
            break;
        case STAT_POISON:
            mPoisonButton.getDrawable().setColorFilter(enabledColor, PorterDuff.Mode.SRC_IN);
            break;
        case STAT_COMMANDER:
            mCommanderButton.getDrawable().setColorFilter(enabledColor, PorterDuff.Mode.SRC_IN);
            break;
        }

        for (LcPlayer player : mPlayers) {
            player.setMode(statMode);
        }
    }

    /**
     * Add a default player to the ArrayList mPlayers. It is given an incremented number name i.e. Player N. The
     * starting life will be either 20 or 40, depending on display mode.
     */
    public void addPlayer() {
        final LcPlayer player = new LcPlayer(this);

        /* Increment the largest player number */
        mLargestPlayerNumber++;
        player.mName = getString(R.string.life_counter_default_name) + " " + mLargestPlayerNumber;
        player.mDefaultLifeTotal = getDefaultLife();
        player.mLife = player.mDefaultLifeTotal;

        mPlayers.add(player);
    }

    /**
     * Add a player from a name and a starting life. Typically this is from a Gathering
     *
     * @param name         The player's name
     * @param startingLife The player's starting life total
     */
    public void addPlayer(String name, int startingLife) {
        LcPlayer player = new LcPlayer(this);
        player.mName = name;
        player.mDefaultLifeTotal = startingLife;
        player.mLife = startingLife;

        /* If the player's name ends in a number, and the number is larger than mPlayerNumber, set mPlayerNumber as
           it */
        try {
            String nameParts[] = player.mName.split(" ");
            int number = Integer.parseInt(nameParts[nameParts.length - 1]);
            if (number > mLargestPlayerNumber) {
                mLargestPlayerNumber = number;
            }
        } catch (NumberFormatException e) {
            /* eat it */
        }

        mPlayers.add(player);
    }

    /**
     * Add a player from a semicolon delimited string of data. The delimited fields are:
     * name; life; life History; poison; poison History; default Life; commander History; commander casting
     * The history entries are comma delimited
     *
     * @param line The string of player data to build an object from
     */
    private void addPlayer(String line) {
        try {
            LcPlayer player = new LcPlayer(this);

            String[] data = line.split(";");

            try {
                player.mName = data[0];
            } catch (Exception e) {
                player.mName = getResources().getString(R.string.life_counter_default_name);
            }

            /* If the player's name ends in a number, and the number is larger than mPlayerNumber, set mPlayerNumber as
               it */
            try {
                String nameParts[] = player.mName.split(" ");
                int number = Integer.parseInt(nameParts[nameParts.length - 1]);
                if (number > mLargestPlayerNumber) {
                    mLargestPlayerNumber = number;
                }
            } catch (NumberFormatException e) {
                /* eat it */
            }

            try {
                player.mLife = Integer.parseInt(data[1]);
            } catch (Exception e) {
                player.mLife = getDefaultLife();
            }

            try {
                player.mDefaultLifeTotal = Integer.parseInt(data[5]);
            } catch (Exception e) {
                player.mDefaultLifeTotal = getDefaultLife();
            }

            try {
                String[] lifeHistory = data[2].split(",");
                player.mLifeHistory = new ArrayList<>(lifeHistory.length);
                HistoryEntry entry;
                for (int i = lifeHistory.length - 1; i >= 0; i--) {
                    entry = new HistoryEntry();
                    entry.mAbsolute = Integer.parseInt(lifeHistory[i]);
                    if (i != lifeHistory.length - 1) {
                        entry.mDelta = entry.mAbsolute - player.mLifeHistory.get(0).mAbsolute;
                    } else {
                        entry.mDelta = entry.mAbsolute - player.mDefaultLifeTotal;
                    }
                    player.mLifeHistory.add(0, entry);
                }
            } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
                player.mLifeHistory = new ArrayList<>();
            }

            try {
                player.mPoison = Integer.parseInt(data[3]);
            } catch (Exception e) {
                player.mPoison = getDefaultLife();
            }

            try {
                String[] poisonHistory = data[4].split(",");
                player.mPoisonHistory = new ArrayList<>(poisonHistory.length);
                HistoryEntry entry;
                for (int i = poisonHistory.length - 1; i >= 0; i--) {
                    entry = new HistoryEntry();
                    entry.mAbsolute = Integer.parseInt(poisonHistory[i]);
                    if (i != poisonHistory.length - 1) {
                        entry.mDelta = entry.mAbsolute - player.mPoisonHistory.get(0).mAbsolute;
                    } else {
                        entry.mDelta = entry.mAbsolute;
                    }
                    player.mPoisonHistory.add(0, entry);
                }

            } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
                player.mPoisonHistory = new ArrayList<>();
            }

            try {
                String[] commanderLifeString = data[6].split(",");
                player.mCommanderDamage = new ArrayList<>(commanderLifeString.length);
                CommanderEntry entry;
                for (String aCommanderLifeString : commanderLifeString) {
                    entry = new CommanderEntry();
                    entry.mLife = Integer.parseInt(aCommanderLifeString);
                    player.mCommanderDamage.add(entry);
                }
            } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
                player.mCommanderDamage = new ArrayList<>();
            }

            try {
                player.mCommanderCasting = Integer.parseInt(data[7]);
            } catch (Exception e) {
                player.mCommanderCasting = 0;
            }

            mPlayers.add(player);
        } catch (ArrayIndexOutOfBoundsException e) {
            /* Eat it */
        }
    }

    /**
     * Return 40 life if in commander mode, 20 life otherwise
     *
     * @return 40 life in commander mode, 20 life otherwise
     */
    private int getDefaultLife() {
        if (mDisplayMode == DISPLAY_COMMANDER) {
            return DEFAULT_LIFE_COMMANDER;
        }
        return DEFAULT_LIFE;
    }

    /**
     * When mTts is initialized, set the boolean flag and display the option in the ActionBar
     *
     * @param status SUCCESS or ERROR.
     */
    @Override
    public void onInit(final int status) {
        if (isAdded()) {
            if (status == TextToSpeech.SUCCESS) {
                int result;
                try {
                    result = mTts.setLanguage(getResources().getConfiguration().locale);
                } catch (IllegalArgumentException e) {
                    /* This is a new exception on Samsung devices, setting the language isn't necessary */
                    result = TextToSpeech.LANG_AVAILABLE;
                }
                if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
                    FamiliarActivity activity = getFamiliarActivity();
                    if (activity != null) {
                        activity.showTtsDialog();
                    }
                } else {
                    mTtsInit = true;
                    if (mIsSearchViewOpen) {
                        /* Search view is open, pend menu refresh */
                        mAfterSearchClosedRunnable = new Runnable() {
                            @Override
                            public void run() {
                                getActivity().invalidateOptionsMenu();
                            }
                        };
                    } else {
                        /* Redraw menu */
                        getActivity().invalidateOptionsMenu();
                    }
                }
            } else if (status == TextToSpeech.ERROR) {
                FamiliarActivity activity = getFamiliarActivity();
                if (activity != null) {
                    activity.showTtsDialog();
                }
            }
        }
    }

    /**
     * Build a LinkedList of all the things to say, which can include TTS calls and MediaPlayer calls. Then call
     * onUtteranceCompleted to start running through the LinkedList, even though no utterance was spoken.
     */
    private void announceLifeTotals() {
        if (mTtsInit) {
            mVocalizations.clear();
            for (LcPlayer p : mPlayers) {
                switch (mStatDisplaying) {
                case STAT_LIFE:
                    if (p.mLife > 9000) {
                        /* If the life is over 9000, split the string on an IMPROBABLE_NUMBER, and insert a call to
                           the m9000Player */
                        String tmp = getResources().getQuantityString(R.plurals.life_counter_spoken_life,
                                IMPROBABLE_NUMBER, p.mName, IMPROBABLE_NUMBER);
                        String parts[] = tmp.split(Integer.toString(IMPROBABLE_NUMBER));
                        mVocalizations.add(parts[0]);
                        mVocalizations.add(OVER_9000_KEY);
                        mVocalizations.add(parts[1]);
                    } else {
                        mVocalizations.add(getResources().getQuantityString(R.plurals.life_counter_spoken_life,
                                p.mLife, p.mName, p.mLife));
                    }
                    break;
                case STAT_POISON:
                    mVocalizations.add(getResources().getQuantityString(R.plurals.life_counter_spoken_poison,
                            p.mPoison, p.mName, p.mPoison));
                    break;
                }

            }

            if (mVocalizations.size() > 0) {
                /* Get the audio focus, and tell everyone else to be quiet for a moment */
                int res = mAudioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
                        AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
                if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
                    onUtteranceCompleted(LIFE_ANNOUNCE);
                }
            }
        }
    }

    /**
     * Necessary to implement OnAudioFocusChangeListener, ignored
     *
     * @param i some irrelevant integer. poor integer
     */
    @Override
    public void onAudioFocusChange(int i) {

    }

    /**
     * This is called every time an utterance is completed, as well as when the m9000Player finishes shouting.
     * It polls an item out of the LinkedList and speaks it, or returns audio focus to the system.
     *
     * @param key A key to determine what was just uttered. This is ignored
     */
    @Override
    public void onUtteranceCompleted(String key) {
        if (mVocalizations.size() > 0) {
            String toSpeak = mVocalizations.poll();
            if (toSpeak.equals(OVER_9000_KEY)) {
                try {
                    m9000Player.stop();
                    m9000Player.prepare();
                    m9000Player.start();
                } catch (IOException e) {
                    /* If the media was not played, fall back to TTSing "over 9000" */
                    HashMap<String, String> ttsParams = new HashMap<>();
                    ttsParams.put(TextToSpeech.Engine.KEY_PARAM_STREAM, String.valueOf(AudioManager.STREAM_MUSIC));
                    ttsParams.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, LIFE_ANNOUNCE);
                    if (mTts.speak(getString(R.string.life_counter_over_9000), TextToSpeech.QUEUE_FLUSH,
                            ttsParams) == TextToSpeech.ERROR) {
                        FamiliarActivity activity = getFamiliarActivity();
                        if (activity != null) {
                            activity.showTtsDialog();
                        }
                    }
                }
            } else {
                HashMap<String, String> ttsParams = new HashMap<>();
                ttsParams.put(TextToSpeech.Engine.KEY_PARAM_STREAM, String.valueOf(AudioManager.STREAM_MUSIC));
                ttsParams.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, LIFE_ANNOUNCE);

                if (mTts.speak(toSpeak, TextToSpeech.QUEUE_FLUSH, ttsParams) == TextToSpeech.ERROR) {
                    FamiliarActivity activity = getFamiliarActivity();
                    if (activity != null) {
                        activity.showTtsDialog();
                    }
                }
            }
        } else {
            mAudioManager.abandonAudioFocus(this);
        }
    }
}