com.if3games.chessonline.DroidFish.java Source code

Java tutorial

Introduction

Here is the source code for com.if3games.chessonline.DroidFish.java

Source

/*
DroidFish - An Android chess program.
Copyright (C) 2011-2013  Peter sterlund, peterosterlund2@gmail.com
Copyright (C) 2012 Leo Mayer
    
This program 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.
    
This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package com.if3games.chessonline;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.TreeMap;

import org.json.JSONException;
import org.json.JSONObject;

import com.google.ads.AdRequest;
import com.google.android.gms.ads.AdView;
import com.google.android.gms.ads.InterstitialAd;
import com.google.android.gms.games.Games;
import com.google.android.gms.games.GamesActivityResultCodes;
import com.google.android.gms.games.GamesStatusCodes;
import com.google.android.gms.games.multiplayer.Invitation;
import com.google.android.gms.games.multiplayer.Multiplayer;
import com.google.android.gms.games.multiplayer.OnInvitationReceivedListener;
import com.google.android.gms.games.multiplayer.Participant;
import com.google.android.gms.games.multiplayer.realtime.RealTimeMessage;
import com.google.android.gms.games.multiplayer.realtime.RealTimeMessageReceivedListener;
import com.google.android.gms.games.multiplayer.realtime.RealTimeMultiplayer.ReliableMessageSentCallback;
import com.google.android.gms.games.multiplayer.realtime.Room;
import com.google.android.gms.games.multiplayer.realtime.RoomConfig;
import com.google.android.gms.games.multiplayer.realtime.RoomStatusUpdateListener;
import com.google.android.gms.games.multiplayer.realtime.RoomUpdateListener;
import com.google.android.gms.plus.Plus;
import com.google.android.gms.appstate.AppStateManager;
import com.google.android.gms.appstate.AppStateStatusCodes;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.example.games.basegameutils.BaseGameActivity;
import com.if3games.chessonline.ChessBoard.SquareDecoration;
import com.if3games.chessonline.activities.CPUWarning;
import com.if3games.chessonline.activities.EditBoard;
import com.if3games.chessonline.activities.EditPGNLoad;
import com.if3games.chessonline.activities.EditPGNSave;
import com.if3games.chessonline.activities.LoadFEN;
import com.if3games.chessonline.activities.LoadScid;
import com.if3games.chessonline.activities.Preferences;
import com.if3games.chessonline.book.BookOptions;
import com.if3games.chessonline.data.ConstantsData;
import com.if3games.chessonline.data.SaveGame;
import com.if3games.chessonline.engine.EngineUtil;
import com.if3games.chessonline.gamelogic.ChessParseError;
import com.if3games.chessonline.gamelogic.DroidChessController;
import com.if3games.chessonline.gamelogic.Move;
import com.if3games.chessonline.gamelogic.Pair;
import com.if3games.chessonline.gamelogic.PgnToken;
import com.if3games.chessonline.gamelogic.Piece;
import com.if3games.chessonline.gamelogic.Position;
import com.if3games.chessonline.gamelogic.TextIO;
import com.if3games.chessonline.gamelogic.TimeControlData;
import com.if3games.chessonline.gamelogic.GameTree.Node;
import com.if3games.chessonline.gtb.Probe;
import com.if3games.chessonline.other.PauseTimer;
import com.larvalabs.svgandroid.SVG;
import com.larvalabs.svgandroid.SVGParser;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.database.SQLException;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.StateListDrawable;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.text.ClipboardManager;
import android.text.Html;
import android.text.Layout;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.BackgroundColorSpan;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.LeadingMarginSpan;
import android.text.style.StyleSpan;
import android.util.Log;
import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.view.View.OnLongClickListener;
import android.view.View.OnTouchListener;
import android.view.WindowManager;
import android.webkit.WebView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView.ScaleType;
import android.widget.FrameLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;

public class DroidFish extends BaseGameActivity
        implements GUIInterface, OnClickListener, RealTimeMessageReceivedListener, RoomStatusUpdateListener,
        RoomUpdateListener, OnInvitationReceivedListener, ReliableMessageSentCallback {
    // FIXME!!! book.txt (and test classes) should not be included in apk

    // FIXME!!! PGN view option: game continuation (for training)
    // FIXME!!! Remove invalid playerActions in PGN import (should be done in verifyChildren)
    // FIXME!!! Implement bookmark mechanism for positions in pgn files
    // FIXME!!! Add support for "Chess Leipzig" font

    // FIXME!!! Computer clock should stop if phone turned off (computer stops thinking if unplugged)
    // FIXME!!! Add support for "no time control" and "hour-glass time control" as defined by the PGN standard

    // FIXME!!! Online play on FICS
    // FIXME!!! Add chess960 support
    // FIXME!!! Implement "hint" feature

    // FIXME!!! Show extended book info. (Win percent, number of games, performance rating, etc.)
    // FIXME!!! Green color for "main move". Red color for "don't play in tournaments" moves.
    // FIXME!!! ECO opening codes

    // FIXME!!! Remember multi-PV analysis setting when program restarted.
    // FIXME!!! Option to display coordinates in border outside chess board.

    // FIXME!!! Better behavior if engine is terminated. How exactly?
    // FIXME!!! Handle PGN non-file intents with more than one game.
    // FIXME!!! Save position to fen/epd file

    // FIXME!!! Strength setting for external engines
    // FIXME!!! Selection dialog for going into variation
    // FIXME!!! Use two engines in engine/engine games

    private ChessBoardPlay cb;
    private static DroidChessController ctrl = null;
    private boolean mShowThinking;
    private boolean mShowStats;
    private boolean mWhiteBasedScores;
    private boolean mShowBookHints;
    private int maxNumArrows;
    private GameMode gameMode;
    private boolean mPonderMode;
    private int timeControl;
    private int movesPerSession;
    private int timeIncrement;
    private int mEngineThreads;
    private String playerName;
    private boolean boardFlipped;
    private boolean autoSwapSides;
    private boolean playerNameFlip;
    private boolean discardVariations;

    private TextView status;
    private ScrollView moveListScroll;
    private TextView moveList;
    private TextView thinking;
    private ImageButton custom1Button, custom2Button, custom3Button;
    private ImageButton modeButton, undoButton, redoButton;
    private ButtonActions custom1ButtonActions, custom2ButtonActions, custom3ButtonActions;
    private TextView whiteTitleText, blackTitleText, engineTitleText;
    private View secondTitleLine;
    private TextView whiteFigText, blackFigText, summaryTitleText;
    private static Dialog moveListMenuDlg;

    SharedPreferences settings;

    private boolean boardGestures;
    private float scrollSensitivity;
    private boolean invertScrollDirection;

    private boolean leftHanded;
    private boolean soundEnabled;
    private MediaPlayer moveSound;
    private boolean vibrateEnabled;
    private boolean animateMoves;
    private boolean autoScrollTitle;
    private boolean showMaterialDiff;
    private boolean showVariationLine;

    private final static String bookDir = "DroidFish";
    private final static String pgnDir = "DroidFish/pgn";
    private final static String fenDir = "DroidFish/epd";
    private final static String engineDir = "DroidFish/uci";
    private final static String gtbDefaultDir = "DroidFish/gtb";
    private BookOptions bookOptions = new BookOptions();
    private PGNOptions pgnOptions = new PGNOptions();
    private EngineOptions engineOptions = new EngineOptions();

    private long lastVisibleMillis; // Time when GUI became invisible. 0 if currently visible.
    private long lastComputationMillis; // Time when engine last showed that it was computing.

    PgnScreenText gameTextListener;

    private WakeLock wakeLock = null;
    private boolean useWakeLock = false;

    private Typeface figNotation;
    private Typeface defaultMoveListTypeFace;
    private Typeface defaultThinkingListTypeFace;

    /** Defines all configurable button actions. */
    private ActionFactory actionFactory = new ActionFactory() {
        private HashMap<String, UIAction> actions;

        private void addAction(UIAction a) {
            actions.put(a.getId(), a);
        }

        {
            actions = new HashMap<String, UIAction>();
            addAction(new UIAction() {
                public String getId() {
                    return "flipboard";
                }

                public int getName() {
                    return R.string.flip_board;
                }

                public int getIcon() {
                    return R.raw.flip;
                }

                public boolean enabled() {
                    return true;
                }

                public void run() {
                    boardFlipped = !cb.flipped;
                    setBooleanPref("boardFlipped", boardFlipped);
                    cb.setFlipped(boardFlipped);
                }
            });
            addAction(new UIAction() {
                public String getId() {
                    return "showThinking";
                }

                public int getName() {
                    return R.string.toggle_show_thinking;
                }

                public int getIcon() {
                    return R.raw.thinking;
                }

                public boolean enabled() {
                    return true;
                }

                public void run() {
                    mShowThinking = toggleBooleanPref("showThinking");
                    updateThinkingInfo();
                }
            });
            addAction(new UIAction() {
                public String getId() {
                    return "bookHints";
                }

                public int getName() {
                    return R.string.toggle_book_hints;
                }

                public int getIcon() {
                    return R.raw.book;
                }

                public boolean enabled() {
                    return true;
                }

                public void run() {
                    mShowBookHints = toggleBooleanPref("bookHints");
                    updateThinkingInfo();
                }
            });
            addAction(new UIAction() {
                public String getId() {
                    return "viewVariations";
                }

                public int getName() {
                    return R.string.toggle_pgn_variations;
                }

                public int getIcon() {
                    return R.raw.variation;
                }

                public boolean enabled() {
                    return true;
                }

                public void run() {
                    pgnOptions.view.variations = toggleBooleanPref("viewVariations");
                    gameTextListener.clear();
                    ctrl.prefsChanged(false);
                }
            });
            addAction(new UIAction() {
                public String getId() {
                    return "viewComments";
                }

                public int getName() {
                    return R.string.toggle_pgn_comments;
                }

                public int getIcon() {
                    return R.raw.comment;
                }

                public boolean enabled() {
                    return true;
                }

                public void run() {
                    pgnOptions.view.comments = toggleBooleanPref("viewComments");
                    gameTextListener.clear();
                    ctrl.prefsChanged(false);
                }
            });
            addAction(new UIAction() {
                public String getId() {
                    return "viewHeaders";
                }

                public int getName() {
                    return R.string.toggle_pgn_headers;
                }

                public int getIcon() {
                    return R.raw.header;
                }

                public boolean enabled() {
                    return true;
                }

                public void run() {
                    pgnOptions.view.headers = toggleBooleanPref("viewHeaders");
                    gameTextListener.clear();
                    ctrl.prefsChanged(false);
                }
            });
            addAction(new UIAction() {
                public String getId() {
                    return "toggleAnalysis";
                }

                public int getName() {
                    return R.string.toggle_analysis;
                }

                public int getIcon() {
                    return R.raw.analyze;
                }

                public boolean enabled() {
                    return true;
                }

                private int oldGameModeType = GameMode.EDIT_GAME;

                public void run() {
                    int gameModeType;
                    if (ctrl.analysisMode()) {
                        gameModeType = oldGameModeType;
                    } else {
                        oldGameModeType = ctrl.getGameMode().getModeNr();
                        gameModeType = GameMode.ANALYSIS;
                    }
                    newGameMode(gameModeType);
                    setBoardFlip(true);
                }
            });
            addAction(new UIAction() {
                public String getId() {
                    return "largeButtons";
                }

                public int getName() {
                    return R.string.toggle_large_buttons;
                }

                public int getIcon() {
                    return R.raw.magnify;
                }

                public boolean enabled() {
                    return true;
                }

                public void run() {
                    pgnOptions.view.headers = toggleBooleanPref("largeButtons");
                    updateButtons();
                }
            });
            addAction(new UIAction() {
                public String getId() {
                    return "blindMode";
                }

                public int getName() {
                    return R.string.blind_mode;
                }

                public int getIcon() {
                    return R.raw.blind;
                }

                public boolean enabled() {
                    return true;
                }

                public void run() {
                    boolean blindMode = !cb.blindMode;
                    setBooleanPref("blindMode", blindMode);
                    cb.setBlindMode(blindMode);
                }
            });
            addAction(new UIAction() {
                public String getId() {
                    return "loadLastFile";
                }

                public int getName() {
                    return R.string.load_last_file;
                }

                public int getIcon() {
                    return R.raw.open_last_file;
                }

                public boolean enabled() {
                    return currFileType() != FT_NONE;
                }

                public void run() {
                    loadLastFile();
                }
            });
            addAction(new UIAction() {
                public String getId() {
                    return "selectEngine";
                }

                public int getName() {
                    return R.string.select_engine;
                }

                public int getIcon() {
                    return R.raw.engine;
                }

                public boolean enabled() {
                    return true;
                }

                public void run() {
                    removeDialog(SELECT_ENGINE_DIALOG_NOMANAGE);
                    showDialog(SELECT_ENGINE_DIALOG_NOMANAGE);
                }
            });
        }

        @Override
        public UIAction getAction(String actionId) {
            return actions.get(actionId);
        }
    };

    // GMS Multiplayer  
    private boolean isSinglePlayer;
    private boolean unlockLetterGridBtn = false;

    final static String TAG = "MULTIPLAER";

    // request codes we use when invoking an external activity
    final int RC_RESOLVE = 5000, RC_UNUSED = 5001;

    // Request codes for the UIs that we show with startActivityForResult:
    final static int RC_SELECT_PLAYERS = 10000;
    final static int RC_INVITATION_INBOX = 10001;
    final static int RC_WAITING_ROOM = 10002;

    // Room ID where the currently active game is taking place; null if we're
    // not playing.
    String mRoomId = null;

    // Are we playing in multiplayer mode?
    boolean mMultiplayer = false;

    // The participants in the currently active game
    ArrayList<Participant> mParticipants = null;

    // My participant ID in the currently active game
    String mMyId = null;

    // If non-null, this is the id of the invitation we received via the
    // invitation listener
    String mIncomingInvitationId = null;

    //private ExpiredTimeDialog nLevelDialog;
    private boolean imReady = false;
    private boolean opponentReady = false;
    private boolean isOpponentResign = false;
    private boolean isOpponentTimeOut = false;
    private boolean isLeaveRoom = false;
    private Handler mHandler;
    private Runnable myRunnable;
    private boolean isMatch = false;

    // For Cloud Save
    private static final int OUR_STATE_KEY = 0;
    // whether we already loaded the state the first time (so we don't reload
    // every time the activity goes to the background and comes back to the foreground)
    boolean mAlreadyLoadedState = false;
    boolean mAlreadyLocalState = false;
    // current save game
    private SaveGame mSaveGame = new SaveGame();
    private GoogleApiClient mClient;
    private Map<String, Integer> mOpponentStats = new HashMap<String, Integer>(ConstantsData.initMap);
    private String opponentName;
    private int opponentRating;
    private int gameTypeMode = 0;
    private int gmsGameVariantNumber = -1;

    private TextView player1TitleText, player2TitleText;

    private int imFirstType = -1;
    private boolean myTurn = false;
    private boolean invalidMove = false;

    private boolean opponentLeave = true;

    private InterstitialAd interstitial;
    private AdRequest adRequest;
    private AdView adView;

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

        int isGMS = getIntent().getExtras().getInt("gms");
        if (isGMS != 1) {
            isSinglePlayer = true;
        } else {
            isSinglePlayer = false;

            GoogleApiClient.Builder builder = new GoogleApiClient.Builder(this);
            builder.addApi(Games.API).addApi(Plus.API).addApi(AppStateManager.API).addScope(Games.SCOPE_GAMES)
                    .addScope(Plus.SCOPE_PLUS_LOGIN).addScope(AppStateManager.SCOPE_APP_STATE);
            mClient = builder.build();

            loadLocal();

            if (isSignedIn()) {
                //onFetchPlayerScoreAndAchive();
                //displayPlayerNameScoreRank();
                //Toast.makeText(this, "I Connected", Toast.LENGTH_SHORT).show();
            }
        }

        Pair<String, String> pair = getPgnOrFenIntent();
        String intentPgnOrFen = pair.first;
        String intentFilename = pair.second;

        createDirectories();

        PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
        settings = PreferenceManager.getDefaultSharedPreferences(this);
        settings.registerOnSharedPreferenceChangeListener(new OnSharedPreferenceChangeListener() {
            @Override
            public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
                try {
                    handlePrefsChange();
                } catch (Exception e) {
                    // TODO: handle exception
                }
            }
        });

        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
        setWakeLock(false);
        wakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "droidfish");
        wakeLock.setReferenceCounted(false);

        custom1ButtonActions = new ButtonActions("custom1", CUSTOM1_BUTTON_DIALOG, R.string.select_action);
        custom2ButtonActions = new ButtonActions("custom2", CUSTOM2_BUTTON_DIALOG, R.string.select_action);
        custom3ButtonActions = new ButtonActions("custom3", CUSTOM3_BUTTON_DIALOG, R.string.select_action);

        figNotation = Typeface.createFromAsset(getAssets(), "fonts/DroidFishChessNotationDark.otf");
        setPieceNames(PGNOptions.PT_LOCAL);
        //requestWindowFeature(Window.FEATURE_NO_TITLE);
        initUI();

        gameTextListener = new PgnScreenText(pgnOptions);
        if (ctrl != null)
            ctrl.shutdownEngine();
        ctrl = new DroidChessController(this, gameTextListener, pgnOptions);
        egtbForceReload = true;
        readPrefs();
        TimeControlData tcData = new TimeControlData();
        tcData.setTimeControl(timeControl, movesPerSession, timeIncrement);
        if (isSinglePlayer) {
            myTurn = true;
            ctrl.newGame(gameMode, tcData);
            {
                byte[] data = null;
                int version = 1;
                if (savedInstanceState != null) {
                    data = savedInstanceState.getByteArray("gameState");
                    version = savedInstanceState.getInt("gameStateVersion", version);
                } else {
                    String dataStr = settings.getString("gameState", null);
                    version = settings.getInt("gameStateVersion", version);
                    if (dataStr != null)
                        data = strToByteArr(dataStr);
                }
                if (data != null)
                    ctrl.fromByteArray(data, version);
            }
            ctrl.setGuiPaused(true);
            ctrl.setGuiPaused(false);
            ctrl.startGame();
            //startNewGame(0);
            if (intentPgnOrFen != null) {
                try {
                    ctrl.setFENOrPGN(intentPgnOrFen);
                    setBoardFlip(true);
                } catch (ChessParseError e) {
                    // If FEN corresponds to illegal chess position, go into edit board mode.
                    try {
                        TextIO.readFEN(intentPgnOrFen);
                    } catch (ChessParseError e2) {
                        if (e2.pos != null)
                            startEditBoard(intentPgnOrFen);
                    }
                }
            } else if (intentFilename != null) {
                if (intentFilename.toLowerCase(Locale.US).endsWith(".fen")
                        || intentFilename.toLowerCase(Locale.US).endsWith(".epd"))
                    loadFENFromFile(intentFilename);
                else
                    loadPGNFromFile(intentFilename);
            }
        } else {
            int rnd = new Random().nextInt(2);
            startMultiplayerGameMode(rnd);
        }
    }

    // Unicode code points for chess pieces
    private static final String figurinePieceNames = Piece.NOTATION_PAWN + " " + Piece.NOTATION_KNIGHT + " "
            + Piece.NOTATION_BISHOP + " " + Piece.NOTATION_ROOK + " " + Piece.NOTATION_QUEEN + " "
            + Piece.NOTATION_KING;

    private final void setPieceNames(int pieceType) {
        if (pieceType == PGNOptions.PT_FIGURINE) {
            TextIO.setPieceNames(figurinePieceNames);
        } else {
            TextIO.setPieceNames(getString(R.string.piece_names));
        }
    }

    /** Create directory structure on SD card. */
    private final void createDirectories() {
        File extDir = Environment.getExternalStorageDirectory();
        String sep = File.separator;
        new File(extDir + sep + bookDir).mkdirs();
        new File(extDir + sep + pgnDir).mkdirs();
        new File(extDir + sep + fenDir).mkdirs();
        new File(extDir + sep + engineDir).mkdirs();
        new File(extDir + sep + gtbDefaultDir).mkdirs();
    }

    /**
     * Return PGN/FEN data or filename from the Intent. Both can not be non-null.
     * @return Pair of PGN/FEN data and filename.
     */
    private final Pair<String, String> getPgnOrFenIntent() {
        String pgnOrFen = null;
        String filename = null;
        try {
            Intent intent = getIntent();
            Uri data = intent.getData();
            if (data == null) {
                Bundle b = intent.getExtras();
                if (b != null) {
                    Object strm = b.get(Intent.EXTRA_STREAM);
                    if (strm instanceof Uri) {
                        data = (Uri) strm;
                        if ("file".equals(data.getScheme())) {
                            filename = data.getEncodedPath();
                            if (filename != null)
                                filename = Uri.decode(filename);
                        }
                    }
                }
            }
            if (data == null) {
                if ((Intent.ACTION_SEND.equals(intent.getAction()) || Intent.ACTION_VIEW.equals(intent.getAction()))
                        && ("application/x-chess-pgn".equals(intent.getType())
                                || "application/x-chess-fen".equals(intent.getType())))
                    pgnOrFen = intent.getStringExtra(Intent.EXTRA_TEXT);
            } else {
                String scheme = intent.getScheme();
                if ("file".equals(scheme)) {
                    filename = data.getEncodedPath();
                    if (filename != null)
                        filename = Uri.decode(filename);
                }
                if ((filename == null) && ("content".equals(scheme) || "file".equals(scheme))) {
                    ContentResolver resolver = getContentResolver();
                    InputStream in = resolver.openInputStream(intent.getData());
                    StringBuilder sb = new StringBuilder();
                    while (true) {
                        byte[] buffer = new byte[16384];
                        int len = in.read(buffer);
                        if (len <= 0)
                            break;
                        sb.append(new String(buffer, 0, len));
                    }
                    pgnOrFen = sb.toString();
                }
            }
        } catch (IOException e) {
            Toast.makeText(getApplicationContext(), R.string.failed_to_read_pgn_data, Toast.LENGTH_SHORT).show();
        }
        return new Pair<String, String>(pgnOrFen, filename);
    }

    private final byte[] strToByteArr(String str) {
        if (str == null)
            return null;
        int nBytes = str.length() / 2;
        byte[] ret = new byte[nBytes];
        for (int i = 0; i < nBytes; i++) {
            int c1 = str.charAt(i * 2) - 'A';
            int c2 = str.charAt(i * 2 + 1) - 'A';
            ret[i] = (byte) (c1 * 16 + c2);
        }
        return ret;
    }

    private final String byteArrToString(byte[] data) {
        if (data == null)
            return null;
        StringBuilder ret = new StringBuilder(32768);
        int nBytes = data.length;
        for (int i = 0; i < nBytes; i++) {
            int b = data[i];
            if (b < 0)
                b += 256;
            char c1 = (char) ('A' + (b / 16));
            char c2 = (char) ('A' + (b & 15));
            ret.append(c1);
            ret.append(c2);
        }
        return ret.toString();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        reInitUI();
    }

    /** Re-initialize UI when layout should change because of rotation or handedness change. */
    private final void reInitUI() {
        ChessBoardPlay oldCB = cb;
        String statusStr = status.getText().toString();
        initUI();
        readPrefs();
        cb.cursorX = oldCB.cursorX;
        cb.cursorY = oldCB.cursorY;
        cb.cursorVisible = oldCB.cursorVisible;
        cb.setPosition(oldCB.pos);
        cb.setFlipped(oldCB.flipped);
        cb.setDrawSquareLabels(oldCB.drawSquareLabels);
        cb.oneTouchMoves = oldCB.oneTouchMoves;
        cb.toggleSelection = oldCB.toggleSelection;
        cb.highlightLastMove = oldCB.highlightLastMove;
        cb.setBlindMode(oldCB.blindMode);
        setSelection(oldCB.selectedSquare);
        cb.userSelectedSquare = oldCB.userSelectedSquare;
        setStatusString(statusStr);
        moveListUpdated();
        updateThinkingInfo();
        ctrl.updateRemainingTime();
        ctrl.updateMaterialDiffList();
    }

    /** Return true if left-handed layout should be used. */
    private final boolean leftHandedView() {
        return settings.getBoolean("leftHanded", false)
                && (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE);
    }

    /** Re-read preferences settings. */
    private final void handlePrefsChange() {
        if (leftHanded != leftHandedView())
            reInitUI();
        else
            readPrefs();
        ctrl.setGameMode(gameMode);
    }

    private final void initUI() {
        leftHanded = leftHandedView();
        if (!isSinglePlayer) {
            setContentView(leftHanded ? R.layout.main_left_handed_gms : R.layout.main_gms);
            for (int id : CLICKABLES) {
                findViewById(id).setOnClickListener(this);
            }
        } else {
            setContentView(leftHanded ? R.layout.main_left_handed : R.layout.main);
        }
        Util.overrideFonts(findViewById(android.R.id.content));

        // title lines need to be regenerated every time due to layout changes (rotations)
        secondTitleLine = findViewById(R.id.second_title_line);
        whiteTitleText = (TextView) findViewById(R.id.white_clock);
        whiteTitleText.setSelected(true);
        blackTitleText = (TextView) findViewById(R.id.black_clock);
        blackTitleText.setSelected(true);
        engineTitleText = (TextView) findViewById(R.id.title_text);
        whiteFigText = (TextView) findViewById(R.id.white_pieces);
        whiteFigText.setTypeface(figNotation);
        whiteFigText.setSelected(true);
        whiteFigText.setTextColor(whiteTitleText.getTextColors());
        blackFigText = (TextView) findViewById(R.id.black_pieces);
        blackFigText.setTypeface(figNotation);
        blackFigText.setSelected(true);
        blackFigText.setTextColor(blackTitleText.getTextColors());
        summaryTitleText = (TextView) findViewById(R.id.title_text_summary);

        player1TitleText = (TextView) findViewById(R.id.player1);
        player2TitleText = (TextView) findViewById(R.id.player2);

        status = (TextView) findViewById(R.id.status);
        moveListScroll = (ScrollView) findViewById(R.id.scrollView);
        moveList = (TextView) findViewById(R.id.moveList);
        defaultMoveListTypeFace = moveList.getTypeface();
        thinking = (TextView) findViewById(R.id.thinking);
        defaultThinkingListTypeFace = thinking.getTypeface();
        status.setFocusable(false);
        moveListScroll.setFocusable(false);
        moveList.setFocusable(false);
        moveList.setMovementMethod(LinkMovementMethod.getInstance());
        thinking.setFocusable(false);

        cb = (ChessBoardPlay) findViewById(R.id.chessboard);
        cb.setFocusable(true);
        cb.requestFocus();
        cb.setClickable(true);
        cb.setPgnOptions(pgnOptions);

        final GestureDetector gd = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
            private float scrollX = 0;
            private float scrollY = 0;

            @Override
            public boolean onDown(MotionEvent e) {
                if (!boardGestures) {
                    handleClick(e);
                    return true;
                }
                scrollX = 0;
                scrollY = 0;
                return false;
            }

            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                if (!boardGestures)
                    return false;
                cb.cancelLongPress();
                if (invertScrollDirection) {
                    distanceX = -distanceX;
                    distanceY = -distanceY;
                }
                if ((scrollSensitivity > 0) && (cb.sqSize > 0)) {
                    scrollX += distanceX;
                    scrollY += distanceY;
                    float scrollUnit = cb.sqSize * scrollSensitivity;
                    if (Math.abs(scrollX) >= Math.abs(scrollY)) {
                        // Undo/redo
                        int nRedo = 0, nUndo = 0;
                        while (scrollX > scrollUnit) {
                            nRedo++;
                            scrollX -= scrollUnit;
                        }
                        while (scrollX < -scrollUnit) {
                            nUndo++;
                            scrollX += scrollUnit;
                        }
                        if (nUndo + nRedo > 0)
                            scrollY = 0;
                        if (nRedo + nUndo > 1) {
                            boolean analysis = gameMode.analysisMode();
                            boolean human = gameMode.playerWhite() || gameMode.playerBlack();
                            if (analysis || !human)
                                ctrl.setGameMode(new GameMode(GameMode.TWO_PLAYERS));
                        }
                        for (int i = 0; i < nRedo; i++)
                            ctrl.redoMove();
                        for (int i = 0; i < nUndo; i++)
                            ctrl.undoMove();
                        ctrl.setGameMode(gameMode);
                    } else {
                        // Next/previous variation
                        int varDelta = 0;
                        while (scrollY > scrollUnit) {
                            varDelta++;
                            scrollY -= scrollUnit;
                        }
                        while (scrollY < -scrollUnit) {
                            varDelta--;
                            scrollY += scrollUnit;
                        }
                        if (varDelta != 0)
                            scrollX = 0;
                        ctrl.changeVariation(varDelta);
                    }
                }
                return true;
            }

            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                if (!boardGestures)
                    return false;
                cb.cancelLongPress();
                handleClick(e);
                return true;
            }

            @Override
            public boolean onDoubleTapEvent(MotionEvent e) {
                if (!boardGestures)
                    return false;
                if (e.getAction() == MotionEvent.ACTION_UP)
                    handleClick(e);
                return true;
            }

            private final void handleClick(MotionEvent e) {
                if (ctrl.humansTurn() && myTurn) {
                    int sq = cb.eventToSquare(e);
                    Move m = cb.mousePressed(sq);
                    if (m != null) {
                        ctrl.makeHumanMove(m);
                        if (!isSinglePlayer) {
                            if (!invalidMove)
                                broadcastMove(m.to, m.from);
                            else
                                invalidMove = false;
                        }
                    }
                    setEgtbHints(cb.getSelectedSquare());
                }
            }

            @Override
            public void onLongPress(MotionEvent e) {
                if (!boardGestures)
                    return;
                ((Vibrator) getSystemService(Context.VIBRATOR_SERVICE)).vibrate(20);
                removeDialog(BOARD_MENU_DIALOG);
                showDialog(BOARD_MENU_DIALOG);
            }
        });
        cb.setOnTouchListener(new OnTouchListener() {
            public boolean onTouch(View v, MotionEvent event) {
                return gd.onTouchEvent(event);
            }
        });
        cb.setOnTrackballListener(new ChessBoard.OnTrackballListener() {
            public void onTrackballEvent(MotionEvent event) {
                if (ctrl.humansTurn()) {
                    Move m = cb.handleTrackballEvent(event);
                    if (m != null)
                        ctrl.makeHumanMove(m);
                    setEgtbHints(cb.getSelectedSquare());
                }
            }
        });

        moveList.setOnLongClickListener(new OnLongClickListener() {
            public boolean onLongClick(View v) {
                removeDialog(MOVELIST_MENU_DIALOG);
                showDialog(MOVELIST_MENU_DIALOG);
                return true;
            }
        });
        thinking.setOnLongClickListener(new OnLongClickListener() {
            public boolean onLongClick(View v) {
                if (mShowThinking || gameMode.analysisMode()) {
                    if (!pvMoves.isEmpty()) {
                        removeDialog(THINKING_MENU_DIALOG);
                        showDialog(THINKING_MENU_DIALOG);
                    }
                }
                return true;
            }
        });

        custom1Button = (ImageButton) findViewById(R.id.custom1Button);
        custom1ButtonActions.setImageButton(custom1Button, this);
        custom2Button = (ImageButton) findViewById(R.id.custom2Button);
        custom2ButtonActions.setImageButton(custom2Button, this);
        custom3Button = (ImageButton) findViewById(R.id.custom3Button);
        custom3ButtonActions.setImageButton(custom3Button, this);

        modeButton = (ImageButton) findViewById(R.id.modeButton);
        modeButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (isSinglePlayer)
                    showDialog(GAME_MODE_DIALOG);
                else
                    showDialog(GAME_GMS_MODE_DIALOG);
            }
        });
        undoButton = (ImageButton) findViewById(R.id.undoButton);
        redoButton = (ImageButton) findViewById(R.id.redoButton);

        if (isSinglePlayer) {
            undoButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    ctrl.undoMove();
                }
            });
            undoButton.setOnLongClickListener(new OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    removeDialog(GO_BACK_MENU_DIALOG);
                    showDialog(GO_BACK_MENU_DIALOG);
                    return true;
                }
            });

            redoButton.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    ctrl.redoMove();
                }
            });
            redoButton.setOnLongClickListener(new OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    removeDialog(GO_FORWARD_MENU_DIALOG);
                    showDialog(GO_FORWARD_MENU_DIALOG);
                    return true;
                }
            });
        } else {
            undoButton.setVisibility(View.GONE);
            redoButton.setVisibility(View.GONE);
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if (isSinglePlayer) {
            if (ctrl != null) {
                byte[] data = ctrl.toByteArray();
                outState.putByteArray("gameState", data);
                outState.putInt("gameStateVersion", 3);
            }
        }
    }

    @Override
    protected void onResume() {
        lastVisibleMillis = 0;
        if (ctrl != null)
            ctrl.setGuiPaused(false);
        notificationActive = true;
        updateNotification();
        setWakeLock(useWakeLock);
        super.onResume();
    }

    @Override
    protected void onPause() {
        if (ctrl != null) {
            ctrl.setGuiPaused(true);
            if (isSinglePlayer) {
                byte[] data = ctrl.toByteArray();
                Editor editor = settings.edit();
                String dataStr = byteArrToString(data);
                editor.putString("gameState", dataStr);
                editor.putInt("gameStateVersion", 3);
                editor.commit();
            }
        }
        lastVisibleMillis = System.currentTimeMillis();
        updateNotification();
        setWakeLock(false);
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        if (ctrl != null)
            ctrl.shutdownEngine();
        setNotification(false);
        super.onDestroy();
    }

    private final int getIntSetting(String settingName, int defaultValue) {
        String tmp = settings.getString(settingName, String.format(Locale.US, "%d", defaultValue));
        int value = Integer.parseInt(tmp);
        return value;
    }

    private final void readPrefs() {
        if (isSinglePlayer) {
            int modeNr = getIntSetting("gameMode", 1);
            gameMode = new GameMode(modeNr);
            String oldPlayerName = playerName;
            playerName = settings.getString("playerName", "Player");
            boardFlipped = settings.getBoolean("boardFlipped", false);
            autoSwapSides = settings.getBoolean("autoSwapSides", false);
            playerNameFlip = settings.getBoolean("playerNameFlip", true);
            setBoardFlip(!playerName.equals(oldPlayerName));
            boolean drawSquareLabels = settings.getBoolean("drawSquareLabels", false);
            cb.setDrawSquareLabels(drawSquareLabels);
            cb.oneTouchMoves = settings.getBoolean("oneTouchMoves", false);
            cb.toggleSelection = getIntSetting("squareSelectType", 0) == 1;
            cb.highlightLastMove = settings.getBoolean("highlightLastMove", true);
            cb.setBlindMode(settings.getBoolean("blindMode", false));

            mShowThinking = settings.getBoolean("showThinking", false);
            mShowStats = settings.getBoolean("showStats", true);
            mWhiteBasedScores = settings.getBoolean("whiteBasedScores", false);
            maxNumArrows = getIntSetting("thinkingArrows", 2);
            mShowBookHints = settings.getBoolean("bookHints", false);

            mEngineThreads = getIntSetting("threads", 1);

            String engine = settings.getString("engine", "stockfish");
            int strength = settings.getInt("strength", 1000);
            setEngineStrength(engine, strength);

            mPonderMode = settings.getBoolean("ponderMode", false);
            if (!mPonderMode)
                ctrl.stopPonder();

            timeControl = getIntSetting("timeControl", 120000);
            movesPerSession = getIntSetting("movesPerSession", 60);
            timeIncrement = getIntSetting("timeIncrement", 0);

            boardGestures = settings.getBoolean("boardGestures", true);
            scrollSensitivity = Float.parseFloat(settings.getString("scrollSensitivity", "2"));
            invertScrollDirection = settings.getBoolean("invertScrollDirection", false);
            discardVariations = settings.getBoolean("discardVariations", false);
            Util.setFullScreenMode(this, settings);
            useWakeLock = settings.getBoolean("wakeLock", false);
            setWakeLock(useWakeLock);

            // Visible Options 

            int fontSize = getIntSetting("fontSize", 12);
            int statusFontSize = fontSize;
            Configuration config = getResources().getConfiguration();
            if (config.orientation == Configuration.ORIENTATION_PORTRAIT)
                statusFontSize = Math.min(statusFontSize, 16);
            status.setTextSize(statusFontSize);
            moveList.setTextSize(fontSize);
            thinking.setTextSize(fontSize);
            soundEnabled = settings.getBoolean("soundEnabled", false);
            vibrateEnabled = settings.getBoolean("vibrateEnabled", false);
            animateMoves = settings.getBoolean("animateMoves", true);
            autoScrollTitle = settings.getBoolean("autoScrollTitle", true);
            setTitleScrolling();

            ColorTheme.instance().readColors(settings);
            cb.setColors();
            Util.overrideFonts(findViewById(android.R.id.content));

            // End Visible Options

            custom1ButtonActions.readPrefs(settings, actionFactory);
            custom2ButtonActions.readPrefs(settings, actionFactory);
            custom3ButtonActions.readPrefs(settings, actionFactory);
            updateButtons();

            bookOptions.filename = settings.getString("bookFile", "");
            bookOptions.maxLength = getIntSetting("bookMaxLength", 1000000);
            bookOptions.preferMainLines = settings.getBoolean("bookPreferMainLines", false);
            bookOptions.tournamentMode = settings.getBoolean("bookTournamentMode", false);
            bookOptions.random = (settings.getInt("bookRandom", 500) - 500) * (3.0 / 500);
            setBookOptions();

            engineOptions.hashMB = getIntSetting("hashMB", 16);
            engineOptions.hints = settings.getBoolean("tbHints", false);
            engineOptions.hintsEdit = settings.getBoolean("tbHintsEdit", false);
            engineOptions.rootProbe = settings.getBoolean("tbRootProbe", true);
            engineOptions.engineProbe = settings.getBoolean("tbEngineProbe", true);
            String gtbPath = settings.getString("gtbPath", "").trim();
            if (gtbPath.length() == 0) {
                File extDir = Environment.getExternalStorageDirectory();
                String sep = File.separator;
                gtbPath = extDir.getAbsolutePath() + sep + gtbDefaultDir;
            }
            engineOptions.gtbPath = gtbPath;
            setEngineOptions(false);
            setEgtbHints(cb.getSelectedSquare());

            updateThinkingInfo();

            pgnOptions.view.variations = settings.getBoolean("viewVariations", true);
            pgnOptions.view.comments = settings.getBoolean("viewComments", true);
            pgnOptions.view.nag = settings.getBoolean("viewNAG", true);
            pgnOptions.view.headers = settings.getBoolean("viewHeaders", false);
            final int oldViewPieceType = pgnOptions.view.pieceType;
            pgnOptions.view.pieceType = getIntSetting("viewPieceType", PGNOptions.PT_LOCAL);
            showVariationLine = settings.getBoolean("showVariationLine", false);
            pgnOptions.imp.variations = settings.getBoolean("importVariations", true);
            pgnOptions.imp.comments = settings.getBoolean("importComments", true);
            pgnOptions.imp.nag = settings.getBoolean("importNAG", true);
            pgnOptions.exp.variations = settings.getBoolean("exportVariations", true);
            pgnOptions.exp.comments = settings.getBoolean("exportComments", true);
            pgnOptions.exp.nag = settings.getBoolean("exportNAG", true);
            pgnOptions.exp.playerAction = settings.getBoolean("exportPlayerAction", false);
            pgnOptions.exp.clockInfo = settings.getBoolean("exportTime", false);

            gameTextListener.clear();
            setPieceNames(pgnOptions.view.pieceType);
            ctrl.prefsChanged(oldViewPieceType != pgnOptions.view.pieceType);
            // update the typeset in case of a change anyway, cause it could occur
            // as well in rotation
            setFigurineNotation(pgnOptions.view.pieceType == PGNOptions.PT_FIGURINE, fontSize);

            showMaterialDiff = settings.getBoolean("materialDiff", false);
            secondTitleLine.setVisibility(showMaterialDiff ? View.VISIBLE : View.GONE);
        } else {
            int modeNr = 1;
            gameMode = new GameMode(modeNr);
            String oldPlayerName = playerName;
            playerName = "Player";
            boardFlipped = false;
            autoSwapSides = false;
            playerNameFlip = true;
            setBoardFlip(!playerName.equals(oldPlayerName));
            boolean drawSquareLabels = false;
            cb.setDrawSquareLabels(drawSquareLabels);
            cb.oneTouchMoves = false;
            cb.toggleSelection = getIntSetting("squareSelectType", 0) == 1;
            cb.highlightLastMove = settings.getBoolean("highlightLastMove", true);
            cb.setBlindMode(false);

            mShowThinking = false;
            mShowStats = settings.getBoolean("showStats", true);
            mWhiteBasedScores = false;
            maxNumArrows = getIntSetting("thinkingArrows", 2);
            mShowBookHints = false;

            mEngineThreads = 1;

            String engine = "stockfish";
            int strength = 1000;
            setEngineStrength(engine, strength);

            mPonderMode = false;
            if (!mPonderMode)
                ctrl.stopPonder();

            // Toto: add time control to gms 
            timeControl = 120000;
            movesPerSession = 60;
            timeIncrement = 0;

            boardGestures = false;
            scrollSensitivity = Float.parseFloat(settings.getString("scrollSensitivity", "2"));
            invertScrollDirection = settings.getBoolean("invertScrollDirection", false);
            discardVariations = false;
            Util.setFullScreenMode(this, settings);
            useWakeLock = false;
            setWakeLock(useWakeLock);

            // Visible Options 

            int fontSize = getIntSetting("fontSize", 12);
            int statusFontSize = fontSize;
            Configuration config = getResources().getConfiguration();
            if (config.orientation == Configuration.ORIENTATION_PORTRAIT)
                statusFontSize = Math.min(statusFontSize, 16);
            status.setTextSize(statusFontSize);
            moveList.setTextSize(fontSize);
            thinking.setTextSize(fontSize);
            soundEnabled = settings.getBoolean("soundEnabled", false);
            vibrateEnabled = settings.getBoolean("vibrateEnabled", false);
            animateMoves = settings.getBoolean("animateMoves", true);
            autoScrollTitle = settings.getBoolean("autoScrollTitle", true);
            setTitleScrolling();

            ColorTheme.instance().readColors(settings);
            cb.setColors();
            Util.overrideFonts(findViewById(android.R.id.content));

            // End Visible Options

            custom1ButtonActions.readPrefs(settings, actionFactory);
            custom2ButtonActions.readPrefs(settings, actionFactory);
            custom3ButtonActions.readPrefs(settings, actionFactory);
            updateButtons();

            bookOptions.filename = "";
            bookOptions.maxLength = 1000000;
            bookOptions.preferMainLines = false;
            bookOptions.tournamentMode = false;
            bookOptions.random = (settings.getInt("bookRandom", 500) - 500) * (3.0 / 500);
            setBookOptions();

            engineOptions.hashMB = 16;
            engineOptions.hints = false;
            engineOptions.hintsEdit = false;
            engineOptions.rootProbe = true;
            engineOptions.engineProbe = true;
            String gtbPath = settings.getString("gtbPath", "").trim();
            if (gtbPath.length() == 0) {
                File extDir = Environment.getExternalStorageDirectory();
                String sep = File.separator;
                gtbPath = extDir.getAbsolutePath() + sep + gtbDefaultDir;
            }
            engineOptions.gtbPath = gtbPath;
            setEngineOptions(false);
            setEgtbHints(cb.getSelectedSquare());

            updateThinkingInfo();

            pgnOptions.view.variations = settings.getBoolean("viewVariations", true);
            pgnOptions.view.comments = settings.getBoolean("viewComments", true);
            pgnOptions.view.nag = settings.getBoolean("viewNAG", true);
            pgnOptions.view.headers = settings.getBoolean("viewHeaders", false);
            final int oldViewPieceType = pgnOptions.view.pieceType;
            pgnOptions.view.pieceType = getIntSetting("viewPieceType", PGNOptions.PT_LOCAL);
            showVariationLine = settings.getBoolean("showVariationLine", false);
            pgnOptions.imp.variations = settings.getBoolean("importVariations", true);
            pgnOptions.imp.comments = settings.getBoolean("importComments", true);
            pgnOptions.imp.nag = settings.getBoolean("importNAG", true);
            pgnOptions.exp.variations = settings.getBoolean("exportVariations", true);
            pgnOptions.exp.comments = settings.getBoolean("exportComments", true);
            pgnOptions.exp.nag = settings.getBoolean("exportNAG", true);
            pgnOptions.exp.playerAction = settings.getBoolean("exportPlayerAction", false);
            pgnOptions.exp.clockInfo = settings.getBoolean("exportTime", false);

            gameTextListener.clear();
            setPieceNames(pgnOptions.view.pieceType);
            ctrl.prefsChanged(oldViewPieceType != pgnOptions.view.pieceType);
            // update the typeset in case of a change anyway, cause it could occur
            // as well in rotation
            setFigurineNotation(pgnOptions.view.pieceType == PGNOptions.PT_FIGURINE, fontSize);

            showMaterialDiff = settings.getBoolean("materialDiff", true);
            secondTitleLine.setVisibility(showMaterialDiff ? View.VISIBLE : View.GONE);
        }

    }

    /**
     * Change the Pieces into figurine or regular (i.e. letters) display
     */
    private final void setFigurineNotation(boolean displayAsFigures, int fontSize) {
        if (displayAsFigures) {
            // increase the font cause it has different kerning and looks small
            float increaseFontSize = fontSize * 1.1f;
            moveList.setTypeface(figNotation);
            moveList.setTextSize(increaseFontSize);
            thinking.setTypeface(figNotation);
            thinking.setTextSize(increaseFontSize);
        } else {
            moveList.setTypeface(defaultMoveListTypeFace);
            thinking.setTypeface(defaultThinkingListTypeFace);
        }
    }

    /** Enable/disable title bar scrolling. */
    private final void setTitleScrolling() {
        TextUtils.TruncateAt where = autoScrollTitle ? TextUtils.TruncateAt.MARQUEE : TextUtils.TruncateAt.END;
        whiteTitleText.setEllipsize(where);
        blackTitleText.setEllipsize(where);
        whiteFigText.setEllipsize(where);
        blackFigText.setEllipsize(where);
    }

    private final void updateButtons() {
        boolean largeButtons = settings.getBoolean("largeButtons", false);
        Resources r = getResources();
        int bWidth = (int) Math
                .round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 36, r.getDisplayMetrics()));
        int bHeight = (int) Math
                .round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 32, r.getDisplayMetrics()));
        if (largeButtons) {
            if (custom1ButtonActions.isEnabled() && custom2ButtonActions.isEnabled()
                    && custom3ButtonActions.isEnabled()) {
                Configuration config = getResources().getConfiguration();
                if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
                    bWidth = bWidth * 6 / 5;
                    bHeight = bHeight * 6 / 5;
                } else {
                    bWidth = bWidth * 5 / 4;
                    bHeight = bHeight * 5 / 4;
                }
            } else {
                bWidth = bWidth * 3 / 2;
                bHeight = bHeight * 3 / 2;
            }
        }
        SVG svg = SVGParser.getSVGFromResource(getResources(), R.raw.touch);
        setButtonData(custom1Button, bWidth, bHeight, custom1ButtonActions.getIcon(), svg);
        setButtonData(custom2Button, bWidth, bHeight, custom2ButtonActions.getIcon(), svg);
        setButtonData(custom3Button, bWidth, bHeight, custom3ButtonActions.getIcon(), svg);
        setButtonData(modeButton, bWidth, bHeight, R.raw.mode, svg);
        setButtonData(undoButton, bWidth, bHeight, R.raw.left, svg);
        setButtonData(redoButton, bWidth, bHeight, R.raw.right, svg);
    }

    private final void setButtonData(ImageButton button, int bWidth, int bHeight, int svgResId, SVG touched) {
        SVG svg = SVGParser.getSVGFromResource(getResources(), svgResId);
        button.setBackgroundDrawable(new SVGPictureDrawable(svg));

        StateListDrawable sld = new StateListDrawable();
        sld.addState(new int[] { android.R.attr.state_pressed }, new SVGPictureDrawable(touched));
        button.setImageDrawable(sld);

        LayoutParams lp = button.getLayoutParams();
        lp.height = bHeight;
        lp.width = bWidth;
        button.setLayoutParams(lp);
        button.setPadding(0, 0, 0, 0);
        button.setScaleType(ScaleType.FIT_XY);
    }

    private synchronized final void setWakeLock(boolean enableLock) {
        WakeLock wl = wakeLock;
        if (wl != null) {
            if (wl.isHeld())
                wl.release();
            if (enableLock)
                wl.acquire();
        }
    }

    private final void setEngineStrength(String engine, int strength) {
        ctrl.setEngineStrength(engine, strength);
        setEngineTitle(engine, strength);
    }

    private final void setEngineTitle(String engine, int strength) {
        if (engine.contains("/")) {
            int idx = engine.lastIndexOf('/');
            String eName = engine.substring(idx + 1);
            engineTitleText.setText(eName);
        } else {
            String eName = getString(
                    engine.equals("cuckoochess") ? R.string.cuckoochess_engine : R.string.stockfish_engine);
            boolean analysis = (ctrl != null) && ctrl.analysisMode();
            if ((strength < 1000) && !analysis) {
                engineTitleText.setText(String.format(Locale.US, "%s: %d%%", eName, strength / 10));
            } else {
                engineTitleText.setText(eName);
            }
        }
    }

    /** Update center field in second header line. */
    public final void updateTimeControlTitle() {
        int[] tmpInfo = ctrl.getTimeLimit();
        StringBuilder sb = new StringBuilder();
        int tc = tmpInfo[0];
        int mps = tmpInfo[1];
        int inc = tmpInfo[2];
        if (mps > 0) {
            sb.append(mps);
            sb.append("/");
        }
        sb.append(timeToString(tc));
        if ((inc > 0) || (mps <= 0)) {
            sb.append("+");
            sb.append(tmpInfo[2] / 1000);
        }
        summaryTitleText.setText(sb.toString());
    }

    @Override
    public void updateEngineTitle() {
        String engine = settings.getString("engine", "stockfish");
        int strength = settings.getInt("strength", 1000);
        setEngineTitle(engine, strength);
    }

    @Override
    public void updateMaterialDifferenceTitle(Util.MaterialDiff diff) {
        whiteFigText.setText(diff.white);
        blackFigText.setText(diff.black);
    }

    private final void setBookOptions() {
        BookOptions options = new BookOptions(bookOptions);
        if (options.filename.length() > 0) {
            File extDir = Environment.getExternalStorageDirectory();
            String sep = File.separator;
            options.filename = extDir.getAbsolutePath() + sep + bookDir + sep + options.filename;
        }
        ctrl.setBookOptions(options);
    }

    private boolean egtbForceReload = false;

    private final void setEngineOptions(boolean restart) {
        computeNetEngineID();
        ctrl.setEngineOptions(new EngineOptions(engineOptions), restart);
        Probe.getInstance().setPath(engineOptions.gtbPath, egtbForceReload);
        egtbForceReload = false;
    }

    private final void computeNetEngineID() {
        String id = "";
        try {
            String engine = settings.getString("engine", "stockfish");
            String[] lines = Util.readFile(engine);
            if (lines.length >= 3)
                id = lines[1] + ":" + lines[2];
        } catch (IOException e) {
        }
        engineOptions.networkID = id;
    }

    private final void setEgtbHints(int sq) {
        if (!engineOptions.hints || (sq < 0)) {
            cb.setSquareDecorations(null);
            return;
        }

        Probe gtbProbe = Probe.getInstance();
        ArrayList<Pair<Integer, Integer>> x = gtbProbe.movePieceProbe(cb.pos, sq);
        if (x == null) {
            cb.setSquareDecorations(null);
            return;
        }

        ArrayList<SquareDecoration> sd = new ArrayList<SquareDecoration>();
        for (Pair<Integer, Integer> p : x)
            sd.add(new SquareDecoration(p.first, p.second));
        cb.setSquareDecorations(sd);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.options_menu, menu);
        if (isSinglePlayer)
            return true;
        else
            return false;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        MenuItem item = menu.findItem(R.id.item_file_menu);
        item.setTitle(boardGestures ? R.string.option_file : R.string.tools_menu);
        if (isSinglePlayer)
            return true;
        else
            return false;
    }

    static private final int RESULT_EDITBOARD = 0;
    static private final int RESULT_SETTINGS = 1;
    static private final int RESULT_LOAD_PGN = 2;
    static private final int RESULT_LOAD_FEN = 3;
    static private final int RESULT_SELECT_SCID = 4;
    static private final int RESULT_OI_PGN_SAVE = 5;
    static private final int RESULT_OI_PGN_LOAD = 6;
    static private final int RESULT_OI_FEN_LOAD = 7;
    static private final int RESULT_GET_FEN = 8;

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.item_new_game:
            showDialog(NEW_GAME_DIALOG);
            return true;
        case R.id.item_editboard: {
            startEditBoard(ctrl.getFEN());
            return true;
        }
        case R.id.item_settings: {
            Intent i = new Intent(DroidFish.this, Preferences.class);
            startActivityForResult(i, RESULT_SETTINGS);
            return true;
        }
        case R.id.item_file_menu: {
            int dialog = boardGestures ? FILE_MENU_DIALOG : BOARD_MENU_DIALOG;
            removeDialog(dialog);
            showDialog(dialog);
            return true;
        }
        case R.id.item_goto_move: {
            showDialog(SELECT_MOVE_DIALOG);
            return true;
        }
        case R.id.item_force_move: {
            ctrl.stopSearch();
            return true;
        }
        case R.id.item_draw: {
            if (ctrl.humansTurn()) {
                if (ctrl.claimDrawIfPossible()) {
                    ctrl.stopPonder();
                } else {
                    Toast.makeText(getApplicationContext(), R.string.offer_draw, Toast.LENGTH_SHORT).show();
                }
            }
            return true;
        }
        case R.id.item_resign: {
            if (ctrl.humansTurn()) {
                ctrl.resignGame();
            }
            return true;
        }
        case R.id.select_book:
            removeDialog(SELECT_BOOK_DIALOG);
            showDialog(SELECT_BOOK_DIALOG);
            return true;
        case R.id.manage_engines:
            showDialog(MANAGE_ENGINES_DIALOG);
            return true;
        case R.id.set_color_theme:
            showDialog(SET_COLOR_THEME_DIALOG);
            return true;
        case R.id.item_about:
            showDialog(ABOUT_DIALOG);
            return true;
        }
        return false;
    }

    private void startEditBoard(String fen) {
        Intent i = new Intent(DroidFish.this, EditBoard.class);
        i.setAction(fen);
        startActivityForResult(i, RESULT_EDITBOARD);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
        case RESULT_SETTINGS:
            handlePrefsChange();
            break;
        case RESULT_EDITBOARD:
            if (resultCode == RESULT_OK) {
                try {
                    String fen = data.getAction();
                    ctrl.setFENOrPGN(fen);
                    setBoardFlip(false);
                } catch (ChessParseError e) {
                }
            }
            break;
        case RESULT_LOAD_PGN:
            if (resultCode == RESULT_OK) {
                try {
                    String pgn = data.getAction();
                    int modeNr = ctrl.getGameMode().getModeNr();
                    if ((modeNr != GameMode.ANALYSIS) && (modeNr != GameMode.EDIT_GAME))
                        newGameMode(GameMode.EDIT_GAME);
                    ctrl.setFENOrPGN(pgn);
                    setBoardFlip(true);
                } catch (ChessParseError e) {
                    Toast.makeText(getApplicationContext(), getParseErrString(e), Toast.LENGTH_SHORT).show();
                }
            }
            break;
        case RESULT_SELECT_SCID:
            if (resultCode == RESULT_OK) {
                String pathName = data.getAction();
                if (pathName != null) {
                    Editor editor = settings.edit();
                    editor.putString("currentScidFile", pathName);
                    editor.putInt("currFT", FT_SCID);
                    editor.commit();
                    Intent i = new Intent(DroidFish.this, LoadScid.class);
                    i.setAction("com.if3games.chessonline.loadScid");
                    i.putExtra("com.if3games.chessonline.pathname", pathName);
                    startActivityForResult(i, RESULT_LOAD_PGN);
                }
            }
            break;
        case RESULT_OI_PGN_LOAD:
            if (resultCode == RESULT_OK) {
                String pathName = getFilePathFromUri(data.getData());
                if (pathName != null)
                    loadPGNFromFile(pathName);
            }
            break;
        case RESULT_OI_PGN_SAVE:
            if (resultCode == RESULT_OK) {
                String pathName = getFilePathFromUri(data.getData());
                if (pathName != null) {
                    if ((pathName.length() > 0) && !pathName.contains("."))
                        pathName += ".pgn";
                    savePGNToFile(pathName, false);
                }
            }
            break;
        case RESULT_OI_FEN_LOAD:
            if (resultCode == RESULT_OK) {
                String pathName = getFilePathFromUri(data.getData());
                if (pathName != null)
                    loadFENFromFile(pathName);
            }
            break;
        case RESULT_GET_FEN:
            if (resultCode == RESULT_OK) {
                String fen = data.getStringExtra(Intent.EXTRA_TEXT);
                if (fen == null) {
                    String pathName = getFilePathFromUri(data.getData());
                    loadFENFromFile(pathName);
                }
                setFenHelper(fen);
            }
            break;
        case RESULT_LOAD_FEN:
            if (resultCode == RESULT_OK) {
                String fen = data.getAction();
                setFenHelper(fen);
            }
            break;
        // GMS
        case RC_SELECT_PLAYERS:
            // we got the result from the "select players" UI -- ready to create the room
            handleSelectPlayersResult(resultCode, data, gmsGameVariantNumber);
            break;
        case RC_INVITATION_INBOX:
            // we got the result from the "select invitation" UI (invitation inbox). We're
            // ready to accept the selected invitation:
            handleInvitationInboxResult(resultCode, data);
            break;
        case RC_WAITING_ROOM:
            // we got the result from the "waiting room" UI.
            if (resultCode == Activity.RESULT_OK) {
                // ready to start playing
                //Log.d(TAG, "Starting game (waiting room returned OK).");
                //if(!imNotFirst)
                //sendImFirstLevelNumberForStart();
                if (gmsGameVariantNumber != -1)
                    startGame(true, gmsGameVariantNumber);
            } else if (resultCode == GamesActivityResultCodes.RESULT_LEFT_ROOM) {
                // player indicated that they want to leave the room
                leaveRoom();
            } else if (resultCode == Activity.RESULT_CANCELED) {
                // Dialog was cancelled (user pressed back key, for instance). In our game,
                // this means leaving the room too. In more elaborate games, this could mean
                // something else (like minimizing the waiting room UI).
                leaveRoom();
            }
            break;
        }
    }

    /** Set new game mode. */
    private final void newGameMode(int gameModeType) {
        Editor editor = settings.edit();
        String gameModeStr = String.format(Locale.US, "%d", gameModeType);
        editor.putString("gameMode", gameModeStr);
        editor.commit();
        gameMode = new GameMode(gameModeType);
        ctrl.setGameMode(gameMode);
    }

    public static String getFilePathFromUri(Uri uri) {
        if (uri == null)
            return null;
        return uri.getPath();
    }

    private final String getParseErrString(ChessParseError e) {
        if (e.resourceId == -1)
            return e.getMessage();
        else
            return getString(e.resourceId);
    }

    private final int nameMatchScore(String name, String match) {
        if (name == null)
            return 0;
        String lName = name.toLowerCase(Locale.US);
        String lMatch = match.toLowerCase(Locale.US);
        if (name.equals(match))
            return 6;
        if (lName.equals(lMatch))
            return 5;
        if (name.startsWith(match))
            return 4;
        if (lName.startsWith(lMatch))
            return 3;
        if (name.contains(match))
            return 2;
        if (lName.contains(lMatch))
            return 1;
        return 0;
    }

    private final void setBoardFlip() {
        setBoardFlip(false);
    }

    /** Set a boolean preference setting. */
    private final void setBooleanPref(String name, boolean value) {
        Editor editor = settings.edit();
        editor.putBoolean(name, value);
        editor.commit();
    }

    /** Toggle a boolean preference setting. Return new value. */
    private final boolean toggleBooleanPref(String name) {
        boolean value = !settings.getBoolean(name, false);
        setBooleanPref(name, value);
        return value;
    }

    private final void setBoardFlip(boolean matchPlayerNames) {
        boolean flipped = boardFlipped;
        if (playerNameFlip && matchPlayerNames && (ctrl != null)) {
            final TreeMap<String, String> headers = new TreeMap<String, String>();
            ctrl.getHeaders(headers);
            int whiteMatch = nameMatchScore(headers.get("White"), playerName);
            int blackMatch = nameMatchScore(headers.get("Black"), playerName);
            if ((flipped && (whiteMatch > blackMatch)) || (!flipped && (whiteMatch < blackMatch))) {
                flipped = !flipped;
                boardFlipped = flipped;
                setBooleanPref("boardFlipped", flipped);
            }
        }
        if (autoSwapSides) {
            if (gameMode.analysisMode()) {
                flipped = !cb.pos.whiteMove;
            } else if (gameMode.playerWhite() && gameMode.playerBlack()) {
                flipped = !cb.pos.whiteMove;
            } else if (gameMode.playerWhite()) {
                flipped = false;
            } else if (gameMode.playerBlack()) {
                flipped = true;
            } else { // two computers
                flipped = !cb.pos.whiteMove;
            }
        }
        cb.setFlipped(flipped);
    }

    @Override
    public void setSelection(int sq) {
        cb.setSelection(cb.highlightLastMove ? sq : -1);
        cb.userSelectedSquare = false;
        setEgtbHints(sq);
    }

    @Override
    public void setStatus(GameStatus s) {
        String str;
        switch (s.state) {
        case ALIVE:
            str = Integer.valueOf(s.moveNr).toString();
            if (s.white)
                str += ". " + getString(R.string.whites_move);
            else
                str += "... " + getString(R.string.blacks_move);
            if (s.ponder)
                str += " (" + getString(R.string.ponder) + ")";
            if (s.thinking)
                str += " (" + getString(R.string.thinking) + ")";
            if (s.analyzing)
                str += " (" + getString(R.string.analyzing) + ")";
            break;
        case WHITE_MATE:
            str = getString(R.string.white_mate);
            if (!isSinglePlayer) {
                if (imFirstType == 0)
                    handleGMSMatchComplete(ConstantsData.GAME_LOSS);
                else
                    handleGMSMatchComplete(ConstantsData.GAME_WON);
            }
            break;
        case BLACK_MATE:
            str = getString(R.string.black_mate);
            if (imFirstType == 1)
                handleGMSMatchComplete(ConstantsData.GAME_LOSS);
            else
                handleGMSMatchComplete(ConstantsData.GAME_WON);
            break;
        case WHITE_STALEMATE:
            str = getString(R.string.stalemate);
            if (!isSinglePlayer)
                handleGMSMatchComplete(ConstantsData.GAME_DRAW);
        case BLACK_STALEMATE:
            str = getString(R.string.stalemate);
            if (!isSinglePlayer)
                handleGMSMatchComplete(ConstantsData.GAME_DRAW);
            break;
        case DRAW_REP: {
            str = getString(R.string.draw_rep);
            if (s.drawInfo.length() > 0)
                str = str + " [" + s.drawInfo + "]";
            if (!isSinglePlayer)
                handleGMSMatchComplete(ConstantsData.GAME_DRAW);
            break;
        }
        case DRAW_50: {
            str = getString(R.string.draw_50);
            if (s.drawInfo.length() > 0)
                str = str + " [" + s.drawInfo + "]";
            break;
        }
        case DRAW_NO_MATE:
            str = getString(R.string.draw_no_mate);
            if (!isSinglePlayer)
                handleGMSMatchComplete(ConstantsData.GAME_DRAW);
            break;
        case DRAW_AGREE:
            str = getString(R.string.draw_agree);
            if (!isSinglePlayer)
                handleGMSMatchComplete(ConstantsData.GAME_DRAW);
            break;
        case RESIGN_WHITE:
            str = getString(R.string.resign_white);
            if (isSinglePlayer) {
                str = getString(R.string.resign_white);
            } else {
                if (!myTurn && (imFirstType == 1) && isOpponentResign) {
                    str = getString(R.string.resign_white);
                    handleGMSMatchComplete(ConstantsData.GAME_WON);
                } else if (!myTurn && (imFirstType == 1) && isOpponentTimeOut) {
                    str = getString(R.string.gms_black_win_time);
                    handleGMSMatchComplete(ConstantsData.GAME_WON);
                } else if (!myTurn && (imFirstType == 1)) {
                    str = getString(R.string.resign_black);
                    handleGMSMatchComplete(ConstantsData.GAME_LOSS);
                } else if (myTurn && (imFirstType == 0) && isOpponentResign) {
                    str = getString(R.string.resign_black);
                    handleGMSMatchComplete(ConstantsData.GAME_WON);
                } else if (myTurn && (imFirstType == 0)) {
                    if ((mSecondsLeft <= 0) && (gmsGameVariantNumber != ConstantsData.GAME_VARIANT_LONG))
                        str = getString(R.string.gms_black_win_time);
                    else
                        str = getString(R.string.resign_white);
                    handleGMSMatchComplete(ConstantsData.GAME_LOSS);
                }
            }
            break;
        case RESIGN_BLACK:
            str = getString(R.string.resign_black);
            if (isSinglePlayer) {
                str = getString(R.string.resign_black);
            } else {
                if (!myTurn && (imFirstType == 0) && isOpponentResign) {
                    str = getString(R.string.resign_black);
                    handleGMSMatchComplete(ConstantsData.GAME_WON);
                } else if (!myTurn && (imFirstType == 0) && isOpponentTimeOut) {
                    str = getString(R.string.gms_white_win_time);
                    handleGMSMatchComplete(ConstantsData.GAME_WON);
                } else if (!myTurn && (imFirstType == 0)) {
                    str = getString(R.string.resign_white);
                    handleGMSMatchComplete(ConstantsData.GAME_LOSS);
                } else if (myTurn && (imFirstType == 1) && isOpponentResign) {
                    str = getString(R.string.resign_white);
                    handleGMSMatchComplete(ConstantsData.GAME_WON);
                } else if (myTurn && (imFirstType == 1)) {
                    if ((mSecondsLeft <= 0) && (gmsGameVariantNumber != ConstantsData.GAME_VARIANT_LONG))
                        str = getString(R.string.gms_white_win_time);
                    else
                        str = getString(R.string.resign_black);
                    handleGMSMatchComplete(ConstantsData.GAME_LOSS);
                }
            }
            break;
        default:
            throw new RuntimeException();
        }
        setStatusString(str);
    }

    private final void setStatusString(String str) {
        status.setText(str);
    }

    @Override
    public void moveListUpdated() {
        moveList.setText(gameTextListener.getSpannableData());
        Layout layout = moveList.getLayout();
        if (layout != null) {
            int currPos = gameTextListener.getCurrPos();
            int line = layout.getLineForOffset(currPos);
            int y = (int) ((line - 1.5) * moveList.getLineHeight());
            moveListScroll.scrollTo(0, y);
        }
    }

    @Override
    public boolean whiteBasedScores() {
        return mWhiteBasedScores;
    }

    @Override
    public boolean ponderMode() {
        return mPonderMode;
    }

    @Override
    public int engineThreads() {
        return mEngineThreads;
    }

    @Override
    public Context getContext() {
        return getApplicationContext();
    }

    @Override
    public String playerName() {
        return playerName;
    }

    @Override
    public boolean discardVariations() {
        return discardVariations;
    }

    /** Report a move made that is a candidate for GUI animation. */
    public void setAnimMove(Position sourcePos, Move move, boolean forward) {
        if (animateMoves && (move != null))
            cb.setAnimMove(sourcePos, move, forward);
    }

    @Override
    public void setPosition(Position pos, String variantInfo, ArrayList<Move> variantMoves) {
        variantStr = variantInfo;
        this.variantMoves = variantMoves;
        cb.setPosition(pos);
        setBoardFlip();
        updateThinkingInfo();
        setEgtbHints(cb.getSelectedSquare());
    }

    private String thinkingStr1 = "";
    private String thinkingStr2 = "";
    private String bookInfoStr = "";
    private String variantStr = "";
    private ArrayList<ArrayList<Move>> pvMoves = new ArrayList<ArrayList<Move>>();
    private ArrayList<Move> bookMoves = null;
    private ArrayList<Move> variantMoves = null;

    @Override
    public void setThinkingInfo(String pvStr, String statStr, String bookInfo, ArrayList<ArrayList<Move>> pvMoves,
            ArrayList<Move> bookMoves) {
        thinkingStr1 = pvStr;
        thinkingStr2 = statStr;
        bookInfoStr = bookInfo;
        this.pvMoves = pvMoves;
        this.bookMoves = bookMoves;
        updateThinkingInfo();

        if (ctrl.computerBusy()) {
            lastComputationMillis = System.currentTimeMillis();
        } else {
            lastComputationMillis = 0;
        }
        updateNotification();
    }

    private final void updateThinkingInfo() {
        boolean thinkingEmpty = true;
        {
            String s = "";
            if (mShowThinking || gameMode.analysisMode()) {
                s = thinkingStr1;
                if (s.length() > 0)
                    thinkingEmpty = false;
                if (mShowStats) {
                    if (!thinkingEmpty)
                        s += "\n";
                    s += thinkingStr2;
                    if (s.length() > 0)
                        thinkingEmpty = false;
                }
            }
            thinking.setText(s, TextView.BufferType.SPANNABLE);
        }
        if (mShowBookHints && (bookInfoStr.length() > 0)) {
            String s = "";
            if (!thinkingEmpty)
                s += "<br>";
            s += Util.boldStart + getString(R.string.book) + Util.boldStop + bookInfoStr;
            thinking.append(Html.fromHtml(s));
            thinkingEmpty = false;
        }
        if (showVariationLine && (variantStr.indexOf(' ') >= 0)) {
            String s = "";
            if (!thinkingEmpty)
                s += "<br>";
            s += Util.boldStart + getString(R.string.variation) + Util.boldStop + variantStr;
            thinking.append(Html.fromHtml(s));
            thinkingEmpty = false;
        }
        thinking.setVisibility(thinkingEmpty ? View.GONE : View.VISIBLE);

        List<Move> hints = null;
        if (mShowThinking || gameMode.analysisMode()) {
            ArrayList<ArrayList<Move>> pvMovesTmp = pvMoves;
            if (pvMovesTmp.size() == 1) {
                hints = pvMovesTmp.get(0);
            } else if (pvMovesTmp.size() > 1) {
                hints = new ArrayList<Move>();
                for (ArrayList<Move> pv : pvMovesTmp)
                    if (!pv.isEmpty())
                        hints.add(pv.get(0));
            }
        }
        if ((hints == null) && mShowBookHints)
            hints = bookMoves;
        if (((hints == null) || hints.isEmpty()) && (variantMoves != null) && variantMoves.size() > 1) {
            hints = variantMoves;
        }
        if ((hints != null) && (hints.size() > maxNumArrows)) {
            hints = hints.subList(0, maxNumArrows);
        }
        cb.setMoveHints(hints);
    }

    static private final int PROMOTE_DIALOG = 0;
    static private final int BOARD_MENU_DIALOG = 1;
    static private final int ABOUT_DIALOG = 2;
    static private final int SELECT_MOVE_DIALOG = 3;
    static private final int SELECT_BOOK_DIALOG = 4;
    static private final int SELECT_ENGINE_DIALOG = 5;
    static private final int SELECT_ENGINE_DIALOG_NOMANAGE = 6;
    static private final int SELECT_PGN_FILE_DIALOG = 7;
    static private final int SELECT_PGN_FILE_SAVE_DIALOG = 8;
    static private final int SET_COLOR_THEME_DIALOG = 9;
    static private final int GAME_MODE_DIALOG = 10;
    static private final int SELECT_PGN_SAVE_NEWFILE_DIALOG = 11;
    static private final int MOVELIST_MENU_DIALOG = 12;
    static private final int THINKING_MENU_DIALOG = 13;
    static private final int GO_BACK_MENU_DIALOG = 14;
    static private final int GO_FORWARD_MENU_DIALOG = 15;
    static private final int FILE_MENU_DIALOG = 16;
    static private final int NEW_GAME_DIALOG = 17;
    static private final int CUSTOM1_BUTTON_DIALOG = 18;
    static private final int CUSTOM2_BUTTON_DIALOG = 19;
    static private final int CUSTOM3_BUTTON_DIALOG = 20;
    static private final int MANAGE_ENGINES_DIALOG = 21;
    static private final int NETWORK_ENGINE_DIALOG = 22;
    static private final int NEW_NETWORK_ENGINE_DIALOG = 23;
    static private final int NETWORK_ENGINE_CONFIG_DIALOG = 24;
    static private final int DELETE_NETWORK_ENGINE_DIALOG = 25;
    static private final int CLIPBOARD_DIALOG = 26;
    static private final int SELECT_FEN_FILE_DIALOG = 27;

    // gms
    static private final int NEW_GMS_GAME_DIALOG = 28;
    static private final int GAME_GMS_MODE_DIALOG = 29;
    static private final int GAME_GMS_DRAW_ASK = 30;
    static private final int GAME_GMS_AUTOMATCH_VARIANT_OPT = 31;
    static private final int GAME_GMS_INVITE_VARIANT_OPT = 32;
    static private final int GAME_GMS_EXIT = 33;

    @Override
    protected Dialog onCreateDialog(int id) {
        switch (id) {
        case NEW_GAME_DIALOG:
            return newGameDialog();
        case PROMOTE_DIALOG:
            return promoteDialog();
        case BOARD_MENU_DIALOG:
            return boardMenuDialog();
        case FILE_MENU_DIALOG:
            return fileMenuDialog();
        case ABOUT_DIALOG:
            return aboutDialog();
        case SELECT_MOVE_DIALOG:
            return selectMoveDialog();
        case SELECT_BOOK_DIALOG:
            return selectBookDialog();
        case SELECT_ENGINE_DIALOG:
            return selectEngineDialog(false);
        case SELECT_ENGINE_DIALOG_NOMANAGE:
            return selectEngineDialog(true);
        case SELECT_PGN_FILE_DIALOG:
            return selectPgnFileDialog();
        case SELECT_PGN_FILE_SAVE_DIALOG:
            return selectPgnFileSaveDialog();
        case SELECT_PGN_SAVE_NEWFILE_DIALOG:
            return selectPgnSaveNewFileDialog();
        case SET_COLOR_THEME_DIALOG:
            return setColorThemeDialog();
        case GAME_MODE_DIALOG:
            return gameModeDialog();
        case MOVELIST_MENU_DIALOG:
            return moveListMenuDialog();
        case THINKING_MENU_DIALOG:
            return thinkingMenuDialog();
        case GO_BACK_MENU_DIALOG:
            return goBackMenuDialog();
        case GO_FORWARD_MENU_DIALOG:
            return goForwardMenuDialog();
        case CUSTOM1_BUTTON_DIALOG:
            return makeButtonDialog(custom1ButtonActions);
        case CUSTOM2_BUTTON_DIALOG:
            return makeButtonDialog(custom2ButtonActions);
        case CUSTOM3_BUTTON_DIALOG:
            return makeButtonDialog(custom3ButtonActions);
        case MANAGE_ENGINES_DIALOG:
            return manageEnginesDialog();
        case NETWORK_ENGINE_DIALOG:
            return networkEngineDialog();
        case NEW_NETWORK_ENGINE_DIALOG:
            return newNetworkEngineDialog();
        case NETWORK_ENGINE_CONFIG_DIALOG:
            return networkEngineConfigDialog();
        case DELETE_NETWORK_ENGINE_DIALOG:
            return deleteNetworkEngineDialog();
        case CLIPBOARD_DIALOG:
            return clipBoardDialog();
        case SELECT_FEN_FILE_DIALOG:
            return selectFenFileDialog();

        case NEW_GMS_GAME_DIALOG:
            return newGmsGameDialog();
        case GAME_GMS_MODE_DIALOG:
            return gameGmsModeDialog();
        case GAME_GMS_DRAW_ASK:
            return drawGmsGameDialog();
        case GAME_GMS_AUTOMATCH_VARIANT_OPT:
            return gameGmsAutoMatchVariantDialog();
        case GAME_GMS_INVITE_VARIANT_OPT:
            return gameGmsInviteVariantDialog();
        case GAME_GMS_EXIT:
            return exitGmsGameDialog();
        }
        return null;
    }

    private final Dialog newGameDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.option_new_game);
        builder.setMessage(R.string.start_new_game);
        builder.setPositiveButton(R.string.yes, new Dialog.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                startNewGame(2);
            }
        });
        builder.setNeutralButton(R.string.white, new Dialog.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                startNewGame(0);
            }
        });
        builder.setNegativeButton(R.string.black, new Dialog.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                startNewGame(1);
            }
        });
        return builder.create();
    }

    private final void startNewGame(int type) {
        if (type != 2) {
            int gameModeType = (type == 0) ? GameMode.PLAYER_WHITE : GameMode.PLAYER_BLACK;
            Editor editor = settings.edit();
            String gameModeStr = String.format(Locale.US, "%d", gameModeType);
            editor.putString("gameMode", gameModeStr);
            editor.commit();
            gameMode = new GameMode(gameModeType);
        }
        //        savePGNToFile(".autosave.pgn", true);
        TimeControlData tcData = new TimeControlData();
        tcData.setTimeControl(timeControl, movesPerSession, timeIncrement);
        ctrl.newGame(gameMode, tcData);
        ctrl.startGame();
        setBoardFlip(true);
        updateEngineTitle();
    }

    private final Dialog promoteDialog() {
        final CharSequence[] items = { getString(R.string.queen), getString(R.string.rook),
                getString(R.string.bishop), getString(R.string.knight) };
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.promote_pawn_to);
        builder.setItems(items, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int item) {
                ctrl.reportPromotePiece(item);
            }
        });
        AlertDialog alert = builder.create();
        return alert;
    }

    private final Dialog clipBoardDialog() {
        final int COPY_GAME = 0;
        final int COPY_POSITION = 1;
        final int PASTE = 2;

        List<CharSequence> lst = new ArrayList<CharSequence>();
        List<Integer> actions = new ArrayList<Integer>();
        lst.add(getString(R.string.copy_game));
        actions.add(COPY_GAME);
        lst.add(getString(R.string.copy_position));
        actions.add(COPY_POSITION);
        lst.add(getString(R.string.paste));
        actions.add(PASTE);
        final List<Integer> finalActions = actions;
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.tools_menu);
        builder.setItems(lst.toArray(new CharSequence[lst.size()]), new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int item) {
                switch (finalActions.get(item)) {
                case COPY_GAME: {
                    String pgn = ctrl.getPGN();
                    ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
                    clipboard.setText(pgn);
                    break;
                }
                case COPY_POSITION: {
                    String fen = ctrl.getFEN() + "\n";
                    ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
                    clipboard.setText(fen);
                    break;
                }
                case PASTE: {
                    ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
                    if (clipboard.hasText()) {
                        String fenPgn = clipboard.getText().toString();
                        try {
                            ctrl.setFENOrPGN(fenPgn);
                            setBoardFlip(true);
                        } catch (ChessParseError e) {
                            Toast.makeText(getApplicationContext(), getParseErrString(e), Toast.LENGTH_SHORT)
                                    .show();
                        }
                    }
                    break;
                }
                }
            }
        });
        AlertDialog alert = builder.create();
        return alert;
    }

    private final Dialog boardMenuDialog() {
        final int CLIPBOARD = 0;
        final int FILEMENU = 1;
        final int SHARE = 2;
        final int GET_FEN = 3;

        List<CharSequence> lst = new ArrayList<CharSequence>();
        List<Integer> actions = new ArrayList<Integer>();
        lst.add(getString(R.string.clipboard));
        actions.add(CLIPBOARD);
        lst.add(getString(R.string.option_file));
        actions.add(FILEMENU);
        lst.add(getString(R.string.share));
        actions.add(SHARE);
        if (hasFenProvider(getPackageManager())) {
            lst.add(getString(R.string.get_fen));
            actions.add(GET_FEN);
        }
        final List<Integer> finalActions = actions;
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.tools_menu);
        builder.setItems(lst.toArray(new CharSequence[lst.size()]), new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int item) {
                switch (finalActions.get(item)) {
                case CLIPBOARD: {
                    showDialog(CLIPBOARD_DIALOG);
                    break;
                }
                case FILEMENU: {
                    removeDialog(FILE_MENU_DIALOG);
                    showDialog(FILE_MENU_DIALOG);
                    break;
                }
                case SHARE: {
                    shareGame();
                    break;
                }
                case GET_FEN:
                    getFen();
                    break;
                }
            }
        });
        AlertDialog alert = builder.create();
        return alert;
    }

    private final void shareGame() {
        Intent i = new Intent(Intent.ACTION_SEND);
        i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
        i.setType("text/plain");
        //i.putExtra(Intent.EXTRA_TEXT, ctrl.getPGN());
        i.putExtra(Intent.EXTRA_TEXT, getString(R.string.app_name) + " " + ConstantsData.MARKET_URL_HTTP);
        startActivity(Intent.createChooser(i, getString(R.string.share_pgn_game)));
    }

    private final Dialog fileMenuDialog() {
        final int LOAD_LAST_FILE = 0;
        final int LOAD_GAME = 1;
        final int LOAD_POS = 2;
        final int LOAD_SCID_GAME = 3;
        final int SAVE_GAME = 4;

        List<CharSequence> lst = new ArrayList<CharSequence>();
        List<Integer> actions = new ArrayList<Integer>();
        if (currFileType() != FT_NONE) {
            lst.add(getString(R.string.load_last_file));
            actions.add(LOAD_LAST_FILE);
        }
        lst.add(getString(R.string.load_game));
        actions.add(LOAD_GAME);
        lst.add(getString(R.string.load_position));
        actions.add(LOAD_POS);
        if (hasScidProvider()) {
            lst.add(getString(R.string.load_scid_game));
            actions.add(LOAD_SCID_GAME);
        }
        lst.add(getString(R.string.save_game));
        actions.add(SAVE_GAME);
        final List<Integer> finalActions = actions;
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.load_save_menu);
        builder.setItems(lst.toArray(new CharSequence[lst.size()]), new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int item) {
                switch (finalActions.get(item)) {
                case LOAD_LAST_FILE:
                    loadLastFile();
                    break;
                case LOAD_GAME:
                    selectFile(R.string.select_pgn_file, R.string.pgn_load, "currentPGNFile", pgnDir,
                            SELECT_PGN_FILE_DIALOG, RESULT_OI_PGN_LOAD);
                    break;
                case SAVE_GAME:
                    selectFile(R.string.select_pgn_file_save, R.string.pgn_save, "currentPGNFile", pgnDir,
                            SELECT_PGN_FILE_SAVE_DIALOG, RESULT_OI_PGN_SAVE);
                    break;
                case LOAD_POS:
                    selectFile(R.string.select_fen_file, R.string.pgn_load, "currentFENFile", fenDir,
                            SELECT_FEN_FILE_DIALOG, RESULT_OI_FEN_LOAD);
                    break;
                case LOAD_SCID_GAME:
                    selectScidFile();
                    break;
                }
            }
        });
        AlertDialog alert = builder.create();
        return alert;
    }

    /** Open dialog to select a game/position from the last used file. */
    final private void loadLastFile() {
        String path = currPathName();
        if (path.length() == 0)
            return;
        switch (currFileType()) {
        case FT_PGN:
            loadPGNFromFile(path);
            break;
        case FT_SCID: {
            Intent data = new Intent(path);
            onActivityResult(RESULT_SELECT_SCID, RESULT_OK, data);
            break;
        }
        case FT_FEN:
            loadFENFromFile(path);
            break;
        }
    }

    private final Dialog aboutDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        String title = getString(R.string.app_name);
        WebView wv = new WebView(this);
        builder.setView(wv);
        InputStream is = getResources().openRawResource(R.raw.about);
        String data = Util.readFromStream(is);
        if (data == null)
            data = "";
        try {
            is.close();
        } catch (IOException e1) {
        }
        wv.loadDataWithBaseURL(null, data, "text/html", "utf-8", null);
        try {
            PackageInfo pi = getPackageManager().getPackageInfo("com.if3games.chessonline", 0);
            title += " " + pi.versionName;
        } catch (NameNotFoundException e) {
        }
        builder.setTitle(title);
        AlertDialog alert = builder.create();
        return alert;
    }

    private final Dialog selectMoveDialog() {
        View content = View.inflate(this, R.layout.select_move_number, null);
        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setView(content);
        builder.setTitle(R.string.goto_move);
        final EditText moveNrView = (EditText) content.findViewById(R.id.selmove_number);
        moveNrView.setText("1");
        final Runnable gotoMove = new Runnable() {
            public void run() {
                try {
                    int moveNr = Integer.parseInt(moveNrView.getText().toString());
                    ctrl.gotoMove(moveNr);
                } catch (NumberFormatException nfe) {
                    Toast.makeText(getApplicationContext(), R.string.invalid_number_format, Toast.LENGTH_SHORT)
                            .show();
                }
            }
        };
        builder.setPositiveButton(android.R.string.ok, new Dialog.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                gotoMove.run();
            }
        });
        builder.setNegativeButton(R.string.cancel, null);

        final AlertDialog dialog = builder.create();

        moveNrView.setOnKeyListener(new OnKeyListener() {
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
                    gotoMove.run();
                    dialog.cancel();
                    return true;
                }
                return false;
            }
        });
        return dialog;
    }

    private final Dialog selectBookDialog() {
        String[] fileNames = findFilesInDirectory(bookDir, new FileNameFilter() {
            @Override
            public boolean accept(String filename) {
                int dotIdx = filename.lastIndexOf(".");
                if (dotIdx < 0)
                    return false;
                String ext = filename.substring(dotIdx + 1);
                return (ext.equals("ctg") || ext.equals("bin"));
            }
        });
        final int numFiles = fileNames.length;
        CharSequence[] items = new CharSequence[numFiles + 1];
        for (int i = 0; i < numFiles; i++)
            items[i] = fileNames[i];
        items[numFiles] = getString(R.string.internal_book);
        final CharSequence[] finalItems = items;
        int defaultItem = numFiles;
        for (int i = 0; i < numFiles; i++) {
            if (bookOptions.filename.equals(items[i])) {
                defaultItem = i;
                break;
            }
        }
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.select_opening_book_file);
        builder.setSingleChoiceItems(items, defaultItem, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int item) {
                Editor editor = settings.edit();
                String bookFile = "";
                if (item < numFiles)
                    bookFile = finalItems[item].toString();
                editor.putString("bookFile", bookFile);
                editor.commit();
                bookOptions.filename = bookFile;
                setBookOptions();
                dialog.dismiss();
            }
        });
        AlertDialog alert = builder.create();
        return alert;
    }

    private final static boolean internalEngine(String name) {
        return "cuckoochess".equals(name) || "stockfish".equals(name);
    }

    private final Dialog selectEngineDialog(final boolean abortOnCancel) {
        String[] fileNames = findFilesInDirectory(engineDir, new FileNameFilter() {
            @Override
            public boolean accept(String filename) {
                return !internalEngine(filename);
            }
        });
        final int numFiles = fileNames.length;
        boolean haveSf = EngineUtil.internalStockFishName() != null;
        final int nEngines = numFiles + 1 + (haveSf ? 1 : 0);
        final String[] items = new String[nEngines];
        final String[] ids = new String[nEngines];
        int idx = 0;
        if (haveSf) {
            ids[idx] = "stockfish";
            items[idx] = getString(R.string.stockfish_engine);
            idx++;
        }
        ids[idx] = "cuckoochess";
        items[idx] = getString(R.string.cuckoochess_engine);
        idx++;
        String sep = File.separator;
        String base = Environment.getExternalStorageDirectory() + sep + engineDir + sep;
        for (int i = 0; i < numFiles; i++) {
            ids[idx] = base + fileNames[i];
            items[idx] = fileNames[i];
            idx++;
        }
        String currEngine = ctrl.getEngine();
        int defaultItem = 0;
        for (int i = 0; i < nEngines; i++) {
            if (ids[i].equals(currEngine)) {
                defaultItem = i;
                break;
            }
        }
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.select_chess_engine);
        builder.setSingleChoiceItems(items, defaultItem, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int item) {
                if ((item < 0) || (item >= nEngines))
                    return;
                Editor editor = settings.edit();
                String engine = ids[item];
                editor.putString("engine", engine);
                editor.commit();
                dialog.dismiss();
                int strength = settings.getInt("strength", 1000);
                setEngineOptions(false);
                setEngineStrength(engine, strength);
            }
        });
        builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                if (!abortOnCancel) {
                    removeDialog(MANAGE_ENGINES_DIALOG);
                    showDialog(MANAGE_ENGINES_DIALOG);
                }
            }
        });
        AlertDialog alert = builder.create();
        return alert;
    }

    private static interface Loader {
        void load(String pathName);
    }

    private final Dialog selectPgnFileDialog() {
        return selectFileDialog(pgnDir, R.string.select_pgn_file, R.string.no_pgn_files, "currentPGNFile",
                new Loader() {
                    @Override
                    public void load(String pathName) {
                        loadPGNFromFile(pathName);
                    }
                });
    }

    private final Dialog selectFenFileDialog() {
        return selectFileDialog(fenDir, R.string.select_fen_file, R.string.no_fen_files, "currentFENFile",
                new Loader() {
                    @Override
                    public void load(String pathName) {
                        loadFENFromFile(pathName);
                    }
                });
    }

    private final Dialog selectFileDialog(final String defaultDir, int selectFileMsg, int noFilesMsg,
            String settingsName, final Loader loader) {
        final String[] fileNames = findFilesInDirectory(defaultDir, null);
        final int numFiles = fileNames.length;
        if (numFiles == 0) {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle(R.string.app_name).setMessage(noFilesMsg);
            AlertDialog alert = builder.create();
            return alert;
        }
        int defaultItem = 0;
        String currentFile = settings.getString(settingsName, "");
        currentFile = new File(currentFile).getName();
        for (int i = 0; i < numFiles; i++) {
            if (currentFile.equals(fileNames[i])) {
                defaultItem = i;
                break;
            }
        }
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(selectFileMsg);
        builder.setSingleChoiceItems(fileNames, defaultItem, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int item) {
                dialog.dismiss();
                String sep = File.separator;
                String fn = fileNames[item].toString();
                String pathName = Environment.getExternalStorageDirectory() + sep + defaultDir + sep + fn;
                loader.load(pathName);
            }
        });
        AlertDialog alert = builder.create();
        return alert;
    }

    private final Dialog selectPgnFileSaveDialog() {
        final String[] fileNames = findFilesInDirectory(pgnDir, null);
        final int numFiles = fileNames.length;
        int defaultItem = 0;
        String currentPGNFile = settings.getString("currentPGNFile", "");
        currentPGNFile = new File(currentPGNFile).getName();
        for (int i = 0; i < numFiles; i++) {
            if (currentPGNFile.equals(fileNames[i])) {
                defaultItem = i;
                break;
            }
        }
        CharSequence[] items = new CharSequence[numFiles + 1];
        for (int i = 0; i < numFiles; i++)
            items[i] = fileNames[i];
        items[numFiles] = getString(R.string.new_file);
        final CharSequence[] finalItems = items;
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.select_pgn_file_save);
        builder.setSingleChoiceItems(finalItems, defaultItem, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int item) {
                String pgnFile;
                if (item >= numFiles) {
                    dialog.dismiss();
                    showDialog(SELECT_PGN_SAVE_NEWFILE_DIALOG);
                } else {
                    dialog.dismiss();
                    pgnFile = fileNames[item].toString();
                    String sep = File.separator;
                    String pathName = Environment.getExternalStorageDirectory() + sep + pgnDir + sep + pgnFile;
                    savePGNToFile(pathName, false);
                }
            }
        });
        AlertDialog alert = builder.create();
        return alert;
    }

    private final Dialog selectPgnSaveNewFileDialog() {
        View content = View.inflate(this, R.layout.create_pgn_file, null);
        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setView(content);
        builder.setTitle(R.string.select_pgn_file_save);
        final EditText fileNameView = (EditText) content.findViewById(R.id.create_pgn_filename);
        fileNameView.setText("");
        final Runnable savePGN = new Runnable() {
            public void run() {
                String pgnFile = fileNameView.getText().toString();
                if ((pgnFile.length() > 0) && !pgnFile.contains("."))
                    pgnFile += ".pgn";
                String sep = File.separator;
                String pathName = Environment.getExternalStorageDirectory() + sep + pgnDir + sep + pgnFile;
                savePGNToFile(pathName, false);
            }
        };
        builder.setPositiveButton(android.R.string.ok, new Dialog.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                savePGN.run();
            }
        });
        builder.setNegativeButton(R.string.cancel, null);

        final Dialog dialog = builder.create();
        fileNameView.setOnKeyListener(new OnKeyListener() {
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
                    savePGN.run();
                    dialog.cancel();
                    return true;
                }
                return false;
            }
        });
        return dialog;
    }

    private final Dialog setColorThemeDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.select_color_theme);
        String[] themeNames = new String[ColorTheme.themeNames.length];
        for (int i = 0; i < themeNames.length; i++)
            themeNames[i] = getString(ColorTheme.themeNames[i]);
        builder.setSingleChoiceItems(themeNames, -1, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int item) {
                ColorTheme.instance().setTheme(settings, item);
                cb.setColors();
                gameTextListener.clear();
                ctrl.prefsChanged(false);
                dialog.dismiss();
                Util.overrideFonts(findViewById(android.R.id.content));
            }
        });
        return builder.create();
    }

    private final Dialog gameModeDialog() {
        final CharSequence[] items = { getString(R.string.analysis_mode), getString(R.string.edit_replay_game),
                getString(R.string.play_white), getString(R.string.play_black), getString(R.string.two_players),
                getString(R.string.comp_vs_comp) };
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.select_game_mode);
        builder.setItems(items, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int item) {
                int gameModeType = -1;
                /* only flip site in case the player was specified resp. changed */
                boolean flipSite = false;
                switch (item) {
                case 0:
                    gameModeType = GameMode.ANALYSIS;
                    break;
                case 1:
                    gameModeType = GameMode.EDIT_GAME;
                    break;
                case 2:
                    gameModeType = GameMode.PLAYER_WHITE;
                    flipSite = true;
                    break;
                case 3:
                    gameModeType = GameMode.PLAYER_BLACK;
                    flipSite = true;
                    break;
                case 4:
                    gameModeType = GameMode.TWO_PLAYERS;
                    break;
                case 5:
                    gameModeType = GameMode.TWO_COMPUTERS;
                    break;
                default:
                    break;
                }
                dialog.dismiss();
                if (gameModeType >= 0) {
                    Editor editor = settings.edit();
                    String gameModeStr = String.format(Locale.US, "%d", gameModeType);
                    editor.putString("gameMode", gameModeStr);
                    editor.commit();
                    gameMode = new GameMode(gameModeType);
                    ctrl.setGameMode(gameMode);
                    setBoardFlip(flipSite);
                }
            }
        });
        AlertDialog alert = builder.create();
        return alert;
    }

    private final Dialog moveListMenuDialog() {
        final int EDIT_HEADERS = 0;
        final int EDIT_COMMENTS = 1;
        final int REMOVE_SUBTREE = 2;
        final int MOVE_VAR_UP = 3;
        final int MOVE_VAR_DOWN = 4;
        final int ADD_NULL_MOVE = 5;

        List<CharSequence> lst = new ArrayList<CharSequence>();
        List<Integer> actions = new ArrayList<Integer>();
        lst.add(getString(R.string.edit_headers));
        actions.add(EDIT_HEADERS);
        if (ctrl.humansTurn()) {
            lst.add(getString(R.string.edit_comments));
            actions.add(EDIT_COMMENTS);
        }
        lst.add(getString(R.string.truncate_gametree));
        actions.add(REMOVE_SUBTREE);
        if (ctrl.numVariations() > 1) {
            lst.add(getString(R.string.move_var_up));
            actions.add(MOVE_VAR_UP);
            lst.add(getString(R.string.move_var_down));
            actions.add(MOVE_VAR_DOWN);
        }

        boolean allowNullMove = gameMode.analysisMode()
                || (gameMode.playerWhite() && gameMode.playerBlack() && !gameMode.clocksActive());
        if (allowNullMove) {
            lst.add(getString(R.string.add_null_move));
            actions.add(ADD_NULL_MOVE);
        }
        final List<Integer> finalActions = actions;
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.edit_game);
        builder.setItems(lst.toArray(new CharSequence[lst.size()]), new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int item) {
                switch (finalActions.get(item)) {
                case EDIT_HEADERS: {
                    final TreeMap<String, String> headers = new TreeMap<String, String>();
                    ctrl.getHeaders(headers);

                    AlertDialog.Builder builder = new AlertDialog.Builder(DroidFish.this);
                    builder.setTitle(R.string.edit_headers);
                    View content = View.inflate(DroidFish.this, R.layout.edit_headers, null);
                    builder.setView(content);

                    final TextView event, site, date, round, white, black;

                    event = (TextView) content.findViewById(R.id.ed_header_event);
                    site = (TextView) content.findViewById(R.id.ed_header_site);
                    date = (TextView) content.findViewById(R.id.ed_header_date);
                    round = (TextView) content.findViewById(R.id.ed_header_round);
                    white = (TextView) content.findViewById(R.id.ed_header_white);
                    black = (TextView) content.findViewById(R.id.ed_header_black);

                    event.setText(headers.get("Event"));
                    site.setText(headers.get("Site"));
                    date.setText(headers.get("Date"));
                    round.setText(headers.get("Round"));
                    white.setText(headers.get("White"));
                    black.setText(headers.get("Black"));

                    builder.setNegativeButton(R.string.cancel, null);
                    builder.setPositiveButton(android.R.string.ok, new Dialog.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            headers.put("Event", event.getText().toString().trim());
                            headers.put("Site", site.getText().toString().trim());
                            headers.put("Date", date.getText().toString().trim());
                            headers.put("Round", round.getText().toString().trim());
                            headers.put("White", white.getText().toString().trim());
                            headers.put("Black", black.getText().toString().trim());
                            ctrl.setHeaders(headers);
                            setBoardFlip(true);
                        }
                    });

                    builder.show();
                    break;
                }
                case EDIT_COMMENTS: {
                    AlertDialog.Builder builder = new AlertDialog.Builder(DroidFish.this);
                    builder.setTitle(R.string.edit_comments);
                    View content = View.inflate(DroidFish.this, R.layout.edit_comments, null);
                    builder.setView(content);

                    DroidChessController.CommentInfo commInfo = ctrl.getComments();

                    final TextView preComment, moveView, nag, postComment;
                    preComment = (TextView) content.findViewById(R.id.ed_comments_pre);
                    moveView = (TextView) content.findViewById(R.id.ed_comments_move);
                    nag = (TextView) content.findViewById(R.id.ed_comments_nag);
                    postComment = (TextView) content.findViewById(R.id.ed_comments_post);

                    preComment.setText(commInfo.preComment);
                    postComment.setText(commInfo.postComment);
                    moveView.setText(commInfo.move);
                    String nagStr = Node.nagStr(commInfo.nag).trim();
                    if ((nagStr.length() == 0) && (commInfo.nag > 0))
                        nagStr = String.format(Locale.US, "%d", commInfo.nag);
                    nag.setText(nagStr);

                    builder.setNegativeButton(R.string.cancel, null);
                    builder.setPositiveButton(android.R.string.ok, new Dialog.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            String pre = preComment.getText().toString().trim();
                            String post = postComment.getText().toString().trim();
                            int nagVal = Node.strToNag(nag.getText().toString());

                            DroidChessController.CommentInfo commInfo = new DroidChessController.CommentInfo();
                            commInfo.preComment = pre;
                            commInfo.postComment = post;
                            commInfo.nag = nagVal;
                            ctrl.setComments(commInfo);
                        }
                    });

                    builder.show();
                    break;
                }
                case REMOVE_SUBTREE:
                    ctrl.removeSubTree();
                    break;
                case MOVE_VAR_UP:
                    ctrl.moveVariation(-1);
                    break;
                case MOVE_VAR_DOWN:
                    ctrl.moveVariation(1);
                    break;
                case ADD_NULL_MOVE:
                    ctrl.makeHumanNullMove();
                    break;
                }
                moveListMenuDlg = null;
            }
        });
        AlertDialog alert = builder.create();
        moveListMenuDlg = alert;
        return alert;
    }

    private final Dialog thinkingMenuDialog() {
        final int ADD_ANALYSIS = 0;
        final int MULTIPV_DEC = 1;
        final int MULTIPV_INC = 2;
        final int HIDE_STATISTICS = 3;
        final int SHOW_STATISTICS = 4;
        List<CharSequence> lst = new ArrayList<CharSequence>();
        List<Integer> actions = new ArrayList<Integer>();
        lst.add(getString(R.string.add_analysis));
        actions.add(ADD_ANALYSIS);
        final int numPV = ctrl.getNumPV();
        if (gameMode.analysisMode()) {
            int maxPV = ctrl.maxPV();
            if (numPV > 1) {
                lst.add(getString(R.string.fewer_variations));
                actions.add(MULTIPV_DEC);
            }
            if (numPV < maxPV) {
                lst.add(getString(R.string.more_variations));
                actions.add(MULTIPV_INC);
            }
        }
        if (thinkingStr1.length() > 0) {
            if (mShowStats) {
                lst.add(getString(R.string.hide_statistics));
                actions.add(HIDE_STATISTICS);
            } else {
                lst.add(getString(R.string.show_statistics));
                actions.add(SHOW_STATISTICS);
            }
        }
        final List<Integer> finalActions = actions;
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.analysis);
        builder.setItems(lst.toArray(new CharSequence[lst.size()]), new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int item) {
                switch (finalActions.get(item)) {
                case ADD_ANALYSIS: {
                    ArrayList<ArrayList<Move>> pvMovesTmp = pvMoves;
                    String[] pvStrs = thinkingStr1.split("\n");
                    for (int i = 0; i < pvMovesTmp.size(); i++) {
                        ArrayList<Move> pv = pvMovesTmp.get(i);
                        StringBuilder preComment = new StringBuilder();
                        if (i < pvStrs.length) {
                            String[] tmp = pvStrs[i].split(" ");
                            for (int j = 0; j < 2; j++) {
                                if (j < tmp.length) {
                                    if (j > 0)
                                        preComment.append(' ');
                                    preComment.append(tmp[j]);
                                }
                            }
                            if (preComment.length() > 0)
                                preComment.append(':');
                        }
                        boolean updateDefault = (i == 0);
                        ctrl.addVariation(preComment.toString(), pv, updateDefault);
                    }
                    break;
                }
                case MULTIPV_DEC:
                    ctrl.setMultiPVMode(numPV - 1);
                    break;
                case MULTIPV_INC:
                    ctrl.setMultiPVMode(numPV + 1);
                    break;
                case HIDE_STATISTICS:
                case SHOW_STATISTICS: {
                    mShowStats = finalActions.get(item) == SHOW_STATISTICS;
                    Editor editor = settings.edit();
                    editor.putBoolean("showStats", mShowStats);
                    editor.commit();
                    updateThinkingInfo();
                    break;
                }
                }
            }
        });
        AlertDialog alert = builder.create();
        return alert;
    }

    private final Dialog goBackMenuDialog() {
        final int GOTO_START_GAME = 0;
        final int GOTO_START_VAR = 1;
        final int GOTO_PREV_VAR = 2;
        final int LOAD_PREV_GAME = 3;

        List<CharSequence> lst = new ArrayList<CharSequence>();
        List<Integer> actions = new ArrayList<Integer>();
        lst.add(getString(R.string.goto_start_game));
        actions.add(GOTO_START_GAME);
        lst.add(getString(R.string.goto_start_variation));
        actions.add(GOTO_START_VAR);
        if (ctrl.currVariation() > 0) {
            lst.add(getString(R.string.goto_prev_variation));
            actions.add(GOTO_PREV_VAR);
        }
        final int currFT = currFileType();
        final String currPathName = currPathName();
        if ((currFT != FT_NONE) && !gameMode.clocksActive()) {
            lst.add(getString(R.string.load_prev_game));
            actions.add(LOAD_PREV_GAME);
        }
        final List<Integer> finalActions = actions;
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.go_back);
        builder.setItems(lst.toArray(new CharSequence[lst.size()]), new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int item) {
                switch (finalActions.get(item)) {
                case GOTO_START_GAME:
                    ctrl.gotoMove(0);
                    break;
                case GOTO_START_VAR:
                    ctrl.gotoStartOfVariation();
                    break;
                case GOTO_PREV_VAR:
                    ctrl.changeVariation(-1);
                    break;
                case LOAD_PREV_GAME:
                    Intent i;
                    if (currFT == FT_PGN) {
                        i = new Intent(DroidFish.this, EditPGNLoad.class);
                        i.setAction("com.if3games.chessonline.loadFilePrevGame");
                        i.putExtra("com.if3games.chessonline.pathname", currPathName);
                        startActivityForResult(i, RESULT_LOAD_PGN);
                    } else if (currFT == FT_SCID) {
                        i = new Intent(DroidFish.this, LoadScid.class);
                        i.setAction("com.if3games.chessonline.loadScidPrevGame");
                        i.putExtra("com.if3games.chessonline.pathname", currPathName);
                        startActivityForResult(i, RESULT_LOAD_PGN);
                    } else if (currFT == FT_FEN) {
                        i = new Intent(DroidFish.this, LoadFEN.class);
                        i.setAction("com.if3games.chessonline.loadPrevFen");
                        i.putExtra("com.if3games.chessonline.pathname", currPathName);
                        startActivityForResult(i, RESULT_LOAD_FEN);
                    }
                    break;
                }
            }
        });
        AlertDialog alert = builder.create();
        return alert;
    }

    private final Dialog goForwardMenuDialog() {
        final int GOTO_END_VAR = 0;
        final int GOTO_NEXT_VAR = 1;
        final int LOAD_NEXT_GAME = 2;

        List<CharSequence> lst = new ArrayList<CharSequence>();
        List<Integer> actions = new ArrayList<Integer>();
        lst.add(getString(R.string.goto_end_variation));
        actions.add(GOTO_END_VAR);
        if (ctrl.currVariation() < ctrl.numVariations() - 1) {
            lst.add(getString(R.string.goto_next_variation));
            actions.add(GOTO_NEXT_VAR);
        }
        final int currFT = currFileType();
        final String currPathName = currPathName();
        if ((currFT != FT_NONE) && !gameMode.clocksActive()) {
            lst.add(getString(R.string.load_next_game));
            actions.add(LOAD_NEXT_GAME);
        }
        final List<Integer> finalActions = actions;
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.go_forward);
        builder.setItems(lst.toArray(new CharSequence[lst.size()]), new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int item) {
                switch (finalActions.get(item)) {
                case GOTO_END_VAR:
                    ctrl.gotoMove(Integer.MAX_VALUE);
                    break;
                case GOTO_NEXT_VAR:
                    ctrl.changeVariation(1);
                    break;
                case LOAD_NEXT_GAME:
                    Intent i;
                    if (currFT == FT_PGN) {
                        i = new Intent(DroidFish.this, EditPGNLoad.class);
                        i.setAction("com.if3games.chessonline.loadFileNextGame");
                        i.putExtra("com.if3games.chessonline.pathname", currPathName);
                        startActivityForResult(i, RESULT_LOAD_PGN);
                    } else if (currFT == FT_SCID) {
                        i = new Intent(DroidFish.this, LoadScid.class);
                        i.setAction("com.if3games.chessonline.loadScidNextGame");
                        i.putExtra("com.if3games.chessonline.pathname", currPathName);
                        startActivityForResult(i, RESULT_LOAD_PGN);
                    } else if (currFT == FT_FEN) {
                        i = new Intent(DroidFish.this, LoadFEN.class);
                        i.setAction("com.if3games.chessonline.loadNextFen");
                        i.putExtra("com.if3games.chessonline.pathname", currPathName);
                        startActivityForResult(i, RESULT_LOAD_FEN);
                    }
                    break;
                }
            }
        });
        AlertDialog alert = builder.create();
        return alert;
    }

    private Dialog makeButtonDialog(ButtonActions buttonActions) {
        List<CharSequence> names = new ArrayList<CharSequence>();
        final List<UIAction> actions = new ArrayList<UIAction>();

        HashSet<String> used = new HashSet<String>();
        for (UIAction a : buttonActions.getMenuActions()) {
            if ((a != null) && a.enabled() && !used.contains(a.getId())) {
                names.add(getString(a.getName()));
                actions.add(a);
                used.add(a.getId());
            }
        }
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(buttonActions.getMenuTitle());
        builder.setItems(names.toArray(new CharSequence[names.size()]), new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int item) {
                UIAction a = actions.get(item);
                a.run();
            }
        });
        return builder.create();
    }

    private final Dialog manageEnginesDialog() {
        final CharSequence[] items = { getString(R.string.select_engine),
                getString(R.string.configure_network_engine) };
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.option_manage_engines);
        builder.setItems(items, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int item) {
                switch (item) {
                case 0:
                    removeDialog(SELECT_ENGINE_DIALOG);
                    showDialog(SELECT_ENGINE_DIALOG);
                    break;
                case 1:
                    removeDialog(NETWORK_ENGINE_DIALOG);
                    showDialog(NETWORK_ENGINE_DIALOG);
                    break;
                }
            }
        });
        AlertDialog alert = builder.create();
        return alert;
    }

    private final Dialog networkEngineDialog() {
        String[] fileNames = findFilesInDirectory(engineDir, new FileNameFilter() {
            @Override
            public boolean accept(String filename) {
                if (internalEngine(filename))
                    return false;
                try {
                    InputStream inStream = new FileInputStream(filename);
                    InputStreamReader inFile = new InputStreamReader(inStream);
                    char[] buf = new char[4];
                    boolean ret = (inFile.read(buf) == 4) && "NETE".equals(new String(buf));
                    inFile.close();
                    return ret;
                } catch (IOException e) {
                    return false;
                }
            }
        });
        final int numFiles = fileNames.length;
        final int numItems = numFiles + 1;
        final String[] items = new String[numItems];
        final String[] ids = new String[numItems];
        int idx = 0;
        String sep = File.separator;
        String base = Environment.getExternalStorageDirectory() + sep + engineDir + sep;
        for (int i = 0; i < numFiles; i++) {
            ids[idx] = base + fileNames[i];
            items[idx] = fileNames[i];
            idx++;
        }
        ids[idx] = "";
        items[idx] = getString(R.string.new_engine);
        idx++;
        String currEngine = ctrl.getEngine();
        int defaultItem = 0;
        for (int i = 0; i < numItems; i++)
            if (ids[i].equals(currEngine)) {
                defaultItem = i;
                break;
            }
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.configure_network_engine);
        builder.setSingleChoiceItems(items, defaultItem, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int item) {
                if ((item < 0) || (item >= numItems))
                    return;
                dialog.dismiss();
                if (item == numItems - 1) {
                    showDialog(NEW_NETWORK_ENGINE_DIALOG);
                } else {
                    networkEngineToConfig = ids[item];
                    removeDialog(NETWORK_ENGINE_CONFIG_DIALOG);
                    showDialog(NETWORK_ENGINE_CONFIG_DIALOG);
                }
            }
        });
        builder.setOnCancelListener(new Dialog.OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                removeDialog(MANAGE_ENGINES_DIALOG);
                showDialog(MANAGE_ENGINES_DIALOG);
            }
        });
        AlertDialog alert = builder.create();
        return alert;
    }

    // Filename of network engine to configure
    private String networkEngineToConfig = "";

    // Ask for name of new network engine
    private final Dialog newNetworkEngineDialog() {
        View content = View.inflate(this, R.layout.create_network_engine, null);
        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setView(content);
        builder.setTitle(R.string.create_network_engine);
        final EditText engineNameView = (EditText) content.findViewById(R.id.create_network_engine);
        engineNameView.setText("");
        final Runnable createEngine = new Runnable() {
            public void run() {
                String engineName = engineNameView.getText().toString();
                String sep = File.separator;
                String pathName = Environment.getExternalStorageDirectory() + sep + engineDir + sep + engineName;
                File file = new File(pathName);
                boolean nameOk = true;
                int errMsg = -1;
                if (engineName.contains("/")) {
                    nameOk = false;
                    errMsg = R.string.slash_not_allowed;
                } else if (internalEngine(engineName) || file.exists()) {
                    nameOk = false;
                    errMsg = R.string.engine_name_in_use;
                }
                if (!nameOk) {
                    Toast.makeText(getApplicationContext(), errMsg, Toast.LENGTH_LONG).show();
                    removeDialog(NETWORK_ENGINE_DIALOG);
                    showDialog(NETWORK_ENGINE_DIALOG);
                    return;
                }
                networkEngineToConfig = pathName;
                removeDialog(NETWORK_ENGINE_CONFIG_DIALOG);
                showDialog(NETWORK_ENGINE_CONFIG_DIALOG);
            }
        };
        builder.setPositiveButton(android.R.string.ok, new Dialog.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                createEngine.run();
            }
        });
        builder.setNegativeButton(R.string.cancel, new Dialog.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                removeDialog(NETWORK_ENGINE_DIALOG);
                showDialog(NETWORK_ENGINE_DIALOG);
            }
        });
        builder.setOnCancelListener(new Dialog.OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                removeDialog(NETWORK_ENGINE_DIALOG);
                showDialog(NETWORK_ENGINE_DIALOG);
            }
        });

        final Dialog dialog = builder.create();
        engineNameView.setOnKeyListener(new OnKeyListener() {
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
                    createEngine.run();
                    dialog.cancel();
                    return true;
                }
                return false;
            }
        });
        return dialog;
    }

    // Configure network engine settings
    private final Dialog networkEngineConfigDialog() {
        View content = View.inflate(this, R.layout.network_engine_config, null);
        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setView(content);
        builder.setTitle(R.string.configure_network_engine);
        final EditText hostNameView = (EditText) content.findViewById(R.id.network_engine_host);
        final EditText portView = (EditText) content.findViewById(R.id.network_engine_port);
        String hostName = "";
        String port = "0";
        try {
            String[] lines = Util.readFile(networkEngineToConfig);
            if ((lines.length >= 1) && lines[0].equals("NETE")) {
                if (lines.length > 1)
                    hostName = lines[1];
                if (lines.length > 2)
                    port = lines[2];
            }
        } catch (IOException e1) {
        }
        hostNameView.setText(hostName);
        portView.setText(port);
        final Runnable writeConfig = new Runnable() {
            public void run() {
                String hostName = hostNameView.getText().toString();
                String port = portView.getText().toString();
                try {
                    FileWriter fw = new FileWriter(new File(networkEngineToConfig), false);
                    fw.write("NETE\n");
                    fw.write(hostName);
                    fw.write("\n");
                    fw.write(port);
                    fw.write("\n");
                    fw.close();
                    setEngineOptions(true);
                } catch (IOException e) {
                    Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG).show();
                }
            }
        };
        builder.setPositiveButton(android.R.string.ok, new Dialog.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                writeConfig.run();
                removeDialog(NETWORK_ENGINE_DIALOG);
                showDialog(NETWORK_ENGINE_DIALOG);
            }
        });
        builder.setNegativeButton(R.string.cancel, new Dialog.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                removeDialog(NETWORK_ENGINE_DIALOG);
                showDialog(NETWORK_ENGINE_DIALOG);
            }
        });
        builder.setOnCancelListener(new Dialog.OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                removeDialog(NETWORK_ENGINE_DIALOG);
                showDialog(NETWORK_ENGINE_DIALOG);
            }
        });
        builder.setNeutralButton(R.string.delete, new Dialog.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                removeDialog(DELETE_NETWORK_ENGINE_DIALOG);
                showDialog(DELETE_NETWORK_ENGINE_DIALOG);
            }
        });

        final Dialog dialog = builder.create();
        portView.setOnKeyListener(new OnKeyListener() {
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
                    writeConfig.run();
                    dialog.cancel();
                    removeDialog(NETWORK_ENGINE_DIALOG);
                    showDialog(NETWORK_ENGINE_DIALOG);
                    return true;
                }
                return false;
            }
        });
        return dialog;
    }

    private Dialog deleteNetworkEngineDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.delete_network_engine);
        String msg = networkEngineToConfig;
        if (msg.lastIndexOf('/') >= 0)
            msg = msg.substring(msg.lastIndexOf('/') + 1);
        builder.setMessage(getString(R.string.network_engine) + ": " + msg);
        builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int id) {
                new File(networkEngineToConfig).delete();
                String engine = settings.getString("engine", "stockfish");
                if (engine.equals(networkEngineToConfig)) {
                    engine = "stockfish";
                    Editor editor = settings.edit();
                    editor.putString("engine", engine);
                    editor.commit();
                    dialog.dismiss();
                    int strength = settings.getInt("strength", 1000);
                    setEngineOptions(false);
                    setEngineStrength(engine, strength);
                }
                dialog.cancel();
                removeDialog(NETWORK_ENGINE_DIALOG);
                showDialog(NETWORK_ENGINE_DIALOG);
            }
        });
        builder.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int id) {
                dialog.cancel();
                removeDialog(NETWORK_ENGINE_DIALOG);
                showDialog(NETWORK_ENGINE_DIALOG);
            }
        });
        builder.setOnCancelListener(new Dialog.OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                removeDialog(NETWORK_ENGINE_DIALOG);
                showDialog(NETWORK_ENGINE_DIALOG);
            }
        });
        AlertDialog alert = builder.create();
        return alert;
    }

    /** Open a load/save file dialog. Uses OI file manager if available. */
    private void selectFile(int titleMsg, int buttonMsg, String settingsName, String defaultDir, int dialog,
            int result) {
        String action = "org.openintents.action.PICK_FILE";
        Intent i = new Intent(action);
        String currentFile = settings.getString(settingsName, "");
        String sep = File.separator;
        if (!currentFile.contains(sep))
            currentFile = Environment.getExternalStorageDirectory() + sep + defaultDir + sep + currentFile;
        i.setData(Uri.fromFile(new File(currentFile)));
        i.putExtra("org.openintents.extra.TITLE", getString(titleMsg));
        i.putExtra("org.openintents.extra.BUTTON_TEXT", getString(buttonMsg));
        try {
            startActivityForResult(i, result);
        } catch (ActivityNotFoundException e) {
            removeDialog(dialog);
            showDialog(dialog);
        }
    }

    private final boolean hasScidProvider() {
        List<ProviderInfo> providers = getPackageManager().queryContentProviders(null, 0, 0);
        for (ProviderInfo info : providers)
            if (info.authority.equals("org.scid.database.scidprovider"))
                return true;
        return false;
    }

    private final void selectScidFile() {
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("org.scid.android", "org.scid.android.SelectFileActivity"));
        intent.setAction(".si4");
        try {
            startActivityForResult(intent, RESULT_SELECT_SCID);
        } catch (ActivityNotFoundException e) {
            Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG).show();
        }
    }

    public final static boolean hasFenProvider(PackageManager manager) {
        Intent i = new Intent(Intent.ACTION_GET_CONTENT);
        i.setType("application/x-chess-fen");
        List<ResolveInfo> resolvers = manager.queryIntentActivities(i, 0);
        return (resolvers != null) && (resolvers.size() > 0);
    }

    private final void getFen() {
        Intent i = new Intent(Intent.ACTION_GET_CONTENT);
        i.setType("application/x-chess-fen");
        try {
            startActivityForResult(i, RESULT_GET_FEN);
        } catch (ActivityNotFoundException e) {
            Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG).show();
        }
    }

    final static int FT_NONE = 0;
    final static int FT_PGN = 1;
    final static int FT_SCID = 2;
    final static int FT_FEN = 3;

    private final int currFileType() {
        return settings.getInt("currFT", FT_NONE);
    }

    /** Return path name for the last used PGN or SCID file. */
    private final String currPathName() {
        int ft = settings.getInt("currFT", FT_NONE);
        switch (ft) {
        case FT_PGN: {
            String ret = settings.getString("currentPGNFile", "");
            String sep = File.separator;
            if (!ret.contains(sep))
                ret = Environment.getExternalStorageDirectory() + sep + pgnDir + sep + ret;
            return ret;
        }
        case FT_SCID:
            return settings.getString("currentScidFile", "");
        case FT_FEN:
            return settings.getString("currentFENFile", "");
        default:
            return "";
        }
    }

    private static interface FileNameFilter {
        boolean accept(String filename);
    }

    private final String[] findFilesInDirectory(String dirName, final FileNameFilter filter) {
        File extDir = Environment.getExternalStorageDirectory();
        String sep = File.separator;
        File dir = new File(extDir.getAbsolutePath() + sep + dirName);
        File[] files = dir.listFiles(new FileFilter() {
            public boolean accept(File pathname) {
                if (!pathname.isFile())
                    return false;
                return (filter == null) || filter.accept(pathname.getAbsolutePath());
            }
        });
        if (files == null)
            files = new File[0];
        final int numFiles = files.length;
        String[] fileNames = new String[numFiles];
        for (int i = 0; i < files.length; i++)
            fileNames[i] = files[i].getName();
        Arrays.sort(fileNames, String.CASE_INSENSITIVE_ORDER);
        return fileNames;
    }

    /** Save current game to a PGN file. */
    private final void savePGNToFile(String pathName, boolean silent) {
        String pgn = ctrl.getPGN();
        Editor editor = settings.edit();
        editor.putString("currentPGNFile", pathName);
        editor.putInt("currFT", FT_PGN);
        editor.commit();
        Intent i = new Intent(DroidFish.this, EditPGNSave.class);
        i.setAction("com.if3games.chessonline.saveFile");
        i.putExtra("com.if3games.chessonline.pathname", pathName);
        i.putExtra("com.if3games.chessonline.pgn", pgn);
        i.putExtra("com.if3games.chessonline.silent", silent);
        startActivity(i);
    }

    /** Load a PGN game from a file. */
    private final void loadPGNFromFile(String pathName) {
        Editor editor = settings.edit();
        editor.putString("currentPGNFile", pathName);
        editor.putInt("currFT", FT_PGN);
        editor.commit();
        Intent i = new Intent(DroidFish.this, EditPGNLoad.class);
        i.setAction("com.if3games.chessonline.loadFile");
        i.putExtra("com.if3games.chessonline.pathname", pathName);
        startActivityForResult(i, RESULT_LOAD_PGN);
    }

    /** Load a FEN position from a file. */
    private final void loadFENFromFile(String pathName) {
        if (pathName == null)
            return;
        Editor editor = settings.edit();
        editor.putString("currentFENFile", pathName);
        editor.putInt("currFT", FT_FEN);
        editor.commit();
        Intent i = new Intent(DroidFish.this, LoadFEN.class);
        i.setAction("com.if3games.chessonline.loadFen");
        i.putExtra("com.if3games.chessonline.pathname", pathName);
        startActivityForResult(i, RESULT_LOAD_FEN);
    }

    private final void setFenHelper(String fen) {
        if (fen == null)
            return;
        try {
            ctrl.setFENOrPGN(fen);
        } catch (ChessParseError e) {
            // If FEN corresponds to illegal chess position, go into edit board mode.
            try {
                TextIO.readFEN(fen);
            } catch (ChessParseError e2) {
                if (e2.pos != null)
                    startEditBoard(fen);
            }
        }
    }

    @Override
    public void requestPromotePiece() {
        showDialog(PROMOTE_DIALOG);
    }

    @Override
    public void reportInvalidMove(Move m) {
        invalidMove = true;
        String msg = String.format(Locale.US, "%s %s-%s", getString(R.string.invalid_move),
                TextIO.squareToString(m.from), TextIO.squareToString(m.to));
        Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void reportEngineName(String engine) {
        String msg = String.format(Locale.US, "%s: %s", getString(R.string.engine), engine);
        if (isSinglePlayer)
            Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void reportEngineError(String errMsg) {
        String msg = String.format(Locale.US, "%s: %s", getString(R.string.engine_error), errMsg);
        Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG).show();
    }

    @Override
    public void computerMoveMade() {
        if (soundEnabled) {
            if (moveSound != null)
                moveSound.release();
            try {
                moveSound = MediaPlayer.create(this, R.raw.movesound);
                if (moveSound != null)
                    moveSound.start();
            } catch (NotFoundException ex) {
            }
        }
        if (vibrateEnabled) {
            Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
            v.vibrate(500);
        }
    }

    @Override
    public void runOnUIThread(Runnable runnable) {
        runOnUiThread(runnable);
    }

    /** Decide if user should be warned about heavy CPU usage. */
    private final void updateNotification() {
        boolean warn = false;
        if (lastVisibleMillis != 0) { // GUI not visible
            warn = lastComputationMillis >= lastVisibleMillis + 90000;
        }
        setNotification(warn);
    }

    private boolean notificationActive = false;

    /** Set/clear the "heavy CPU usage" notification. */
    private final void setNotification(boolean show) {
        if (notificationActive == show)
            return;
        notificationActive = show;
        final int cpuUsage = 1;
        String ns = Context.NOTIFICATION_SERVICE;
        NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns);
        if (show) {
            int icon = R.drawable.icon;
            CharSequence tickerText = getString(R.string.heavy_cpu_usage);
            long when = System.currentTimeMillis();
            Notification notification = new Notification(icon, tickerText, when);
            notification.flags |= Notification.FLAG_ONGOING_EVENT;

            Context context = getApplicationContext();
            CharSequence contentTitle = getString(R.string.background_processing);
            CharSequence contentText = getString(R.string.lot_cpu_power);
            Intent notificationIntent = new Intent(this, CPUWarning.class);

            PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
            notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);

            mNotificationManager.notify(cpuUsage, notification);
        } else {
            mNotificationManager.cancel(cpuUsage);
        }
    }

    private final String timeToString(int time) {
        int secs = (int) Math.floor((time + 999) / 1000.0);
        boolean neg = false;
        if (secs < 0) {
            neg = true;
            secs = -secs;
        }
        int mins = secs / 60;
        secs -= mins * 60;
        StringBuilder ret = new StringBuilder();
        if (neg)
            ret.append('-');
        ret.append(mins);
        ret.append(':');
        if (secs < 10)
            ret.append('0');
        ret.append(secs);
        return ret.toString();
    }

    private Handler handlerTimer = new Handler();
    private Runnable r = new Runnable() {
        public void run() {
            ctrl.updateRemainingTime();
        }
    };

    @Override
    public void setRemainingTime(int wTime, int bTime, int nextUpdate) {
        if (ctrl.getGameMode().clocksActive()) {
            whiteTitleText.setText(getString(R.string.white_square_character) + " " + timeToString(wTime));
            blackTitleText.setText(getString(R.string.black_square_character) + " " + timeToString(bTime));
        } else {
            TreeMap<String, String> headers = new TreeMap<String, String>();
            ctrl.getHeaders(headers);
            whiteTitleText.setText(headers.get("White"));
            blackTitleText.setText(headers.get("Black"));
        }
        handlerTimer.removeCallbacks(r);
        if (nextUpdate > 0)
            handlerTimer.postDelayed(r, nextUpdate);
    }

    /** PngTokenReceiver implementation that renders PGN data for screen display. */
    static class PgnScreenText implements PgnToken.PgnTokenReceiver {
        private SpannableStringBuilder sb = new SpannableStringBuilder();
        private int prevType = PgnToken.EOF;
        int nestLevel = 0;
        boolean col0 = true;
        Node currNode = null;
        final static int indentStep = 15;
        int currPos = 0, endPos = 0;
        boolean upToDate = false;
        PGNOptions options;

        private static class NodeInfo {
            int l0, l1;

            NodeInfo(int ls, int le) {
                l0 = ls;
                l1 = le;
            }
        }

        HashMap<Node, NodeInfo> nodeToCharPos;

        PgnScreenText(PGNOptions options) {
            nodeToCharPos = new HashMap<Node, NodeInfo>();
            this.options = options;
        }

        public final SpannableStringBuilder getSpannableData() {
            return sb;
        }

        public final int getCurrPos() {
            return currPos;
        }

        public boolean isUpToDate() {
            return upToDate;
        }

        int paraStart = 0;
        int paraIndent = 0;
        boolean paraBold = false;

        private final void newLine() {
            newLine(false);
        }

        private final void newLine(boolean eof) {
            if (!col0) {
                if (paraIndent > 0) {
                    int paraEnd = sb.length();
                    int indent = paraIndent * indentStep;
                    sb.setSpan(new LeadingMarginSpan.Standard(indent), paraStart, paraEnd,
                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                }
                if (paraBold) {
                    int paraEnd = sb.length();
                    sb.setSpan(new StyleSpan(Typeface.BOLD), paraStart, paraEnd,
                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                }
                if (!eof)
                    sb.append('\n');
                paraStart = sb.length();
                paraIndent = nestLevel;
                paraBold = false;
            }
            col0 = true;
        }

        boolean pendingNewLine = false;

        /** Makes moves in the move list clickable. */
        private final static class MoveLink extends ClickableSpan {
            private Node node;

            MoveLink(Node n) {
                node = n;
            }

            @Override
            public void onClick(View widget) {
                if (ctrl != null) {
                    // On android 4.1 this onClick method is called
                    // even when you long click the move list. The test
                    // below works around the problem.
                    Dialog mlmd = moveListMenuDlg;
                    if ((mlmd == null) || !mlmd.isShowing())
                        ctrl.goNode(node);
                }
            }

            @Override
            public void updateDrawState(TextPaint ds) {
            }
        }

        public void processToken(Node node, int type, String token) {
            if ((prevType == PgnToken.RIGHT_BRACKET) && (type != PgnToken.LEFT_BRACKET)) {
                if (options.view.headers) {
                    col0 = false;
                    newLine();
                } else {
                    sb.clear();
                    paraBold = false;
                }
            }
            if (pendingNewLine) {
                if (type != PgnToken.RIGHT_PAREN) {
                    newLine();
                    pendingNewLine = false;
                }
            }
            switch (type) {
            case PgnToken.STRING:
                sb.append(" \"");
                sb.append(token);
                sb.append('"');
                break;
            case PgnToken.INTEGER:
                if ((prevType != PgnToken.LEFT_PAREN) && (prevType != PgnToken.RIGHT_BRACKET) && !col0)
                    sb.append(' ');
                sb.append(token);
                col0 = false;
                break;
            case PgnToken.PERIOD:
                sb.append('.');
                col0 = false;
                break;
            case PgnToken.ASTERISK:
                sb.append(" *");
                col0 = false;
                break;
            case PgnToken.LEFT_BRACKET:
                sb.append('[');
                col0 = false;
                break;
            case PgnToken.RIGHT_BRACKET:
                sb.append("]\n");
                col0 = false;
                break;
            case PgnToken.LEFT_PAREN:
                nestLevel++;
                if (col0)
                    paraIndent++;
                newLine();
                sb.append('(');
                col0 = false;
                break;
            case PgnToken.RIGHT_PAREN:
                sb.append(')');
                nestLevel--;
                pendingNewLine = true;
                break;
            case PgnToken.NAG:
                sb.append(Node.nagStr(Integer.parseInt(token)));
                col0 = false;
                break;
            case PgnToken.SYMBOL: {
                if ((prevType != PgnToken.RIGHT_BRACKET) && (prevType != PgnToken.LEFT_BRACKET) && !col0)
                    sb.append(' ');
                int l0 = sb.length();
                sb.append(token);
                int l1 = sb.length();
                nodeToCharPos.put(node, new NodeInfo(l0, l1));
                sb.setSpan(new MoveLink(node), l0, l1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                if (endPos < l0)
                    endPos = l0;
                col0 = false;
                if (nestLevel == 0)
                    paraBold = true;
                break;
            }
            case PgnToken.COMMENT:
                if (prevType == PgnToken.RIGHT_BRACKET) {
                } else if (nestLevel == 0) {
                    nestLevel++;
                    newLine();
                    nestLevel--;
                } else {
                    if ((prevType != PgnToken.LEFT_PAREN) && !col0) {
                        sb.append(' ');
                    }
                }
                int l0 = sb.length();
                sb.append(token.replaceAll("[ \t\r\n]+", " ").trim());
                int l1 = sb.length();
                int color = ColorTheme.instance().getColor(ColorTheme.PGN_COMMENT);
                sb.setSpan(new ForegroundColorSpan(color), l0, l1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                col0 = false;
                if (nestLevel == 0)
                    newLine();
                break;
            case PgnToken.EOF:
                newLine(true);
                upToDate = true;
                break;
            }
            prevType = type;
        }

        @Override
        public void clear() {
            sb.clear();
            prevType = PgnToken.EOF;
            nestLevel = 0;
            col0 = true;
            currNode = null;
            currPos = 0;
            endPos = 0;
            nodeToCharPos.clear();
            paraStart = 0;
            paraIndent = 0;
            paraBold = false;
            pendingNewLine = false;

            upToDate = false;
        }

        BackgroundColorSpan bgSpan = new BackgroundColorSpan(0xff888888);

        @Override
        public void setCurrent(Node node) {
            sb.removeSpan(bgSpan);
            NodeInfo ni = nodeToCharPos.get(node);
            if ((ni == null) && (node != null) && (node.getParent() != null))
                ni = nodeToCharPos.get(node.getParent());
            if (ni != null) {
                int color = ColorTheme.instance().getColor(ColorTheme.CURRENT_MOVE);
                bgSpan = new BackgroundColorSpan(color);
                sb.setSpan(bgSpan, ni.l0, ni.l1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                currPos = ni.l0;
            } else {
                currPos = 0;
            }
            currNode = node;
        }
    }

    /*
     * GMS Logic Initial SECTION. Methods that implement the game's multiplayer.
     */

    private void startMultiplayerGameMode(int n) {
        isMatch = true;
        if (mHandler != null) {
            mHandler.removeCallbacks(myRunnable);
            mHandler = null;
            myRunnable = null;
        }
        if (n == 0)
            myTurn = true;
        else
            myTurn = false;
        if (n >= 0 && n <= 1)
            startNewGame(n);

        int gameModeType = -1;
        /* only flip site in case the player was specified resp. changed */
        boolean flipSite = false;
        gameModeType = GameMode.TWO_PLAYERS;

        if (gameModeType >= 0) {
            Editor editor = settings.edit();
            String gameModeStr = String.format(Locale.US, "%d", gameModeType);
            editor.putString("gameMode", gameModeStr);
            editor.commit();
            GameMode gmsGameMode = new GameMode(gameModeType);
            ctrl.setGameMode(gmsGameMode);
            setBoardFlip(flipSite);
        }
        if (mSaveGame.getStatsFromName(ConstantsData.CH_KEY_PLAYED) == 0)
            loadFromCloud();
        else {
            player1TitleText.setText(getString(R.string.str_gms_you_title) + "("
                    + Integer.toString(mSaveGame.getStatsFromName("rating")) + ")");
            player2TitleText.setText(opponentName + "(" + Integer.toString(opponentRating) + ")");
        }
        if (mSaveGame.getStatsFromName(ConstantsData.CH_KEY_PLAYED) == 0) {
            player1TitleText.setText(getString(R.string.str_gms_you_title));
            player2TitleText.setText(opponentName);
        }
    }

    /*
     * GMS Network SECTION. Methods that implement the game's multiplayer.
     */

    @Override
    public void onP2PConnected(String arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onP2PDisconnected(String arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onPeerDeclined(Room arg0, List<String> arg1) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onPeerInvitedToRoom(Room arg0, List<String> arg1) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onPeerJoined(Room arg0, List<String> arg1) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onPeerLeft(Room arg0, List<String> arg1) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onPeersConnected(Room arg0, List<String> arg1) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onPeersDisconnected(Room arg0, List<String> arg1) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onRoomAutoMatching(Room arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onRoomConnecting(Room arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onClick(View v) {
        Intent intent;

        switch (v.getId()) {
        case R.id.button_sign_in:
            // user wants to sign in
            beginUserInitiatedSignIn();
            break;
        case R.id.button_sign_out:
            // user wants to sign out
            signOut();
            switchToScreen(R.id.screen_sign_in);
            break;
        case R.id.button_invite_players:
            if (!mAlreadyLoadedState)
                loadFromCloud();

            showDialog(GAME_GMS_INVITE_VARIANT_OPT);
            break;
        case R.id.button_see_invitations:
            if (!mAlreadyLoadedState)
                loadFromCloud();
            // show list of pending invitations
            intent = Games.Invitations.getInvitationInboxIntent(getApiClient());
            switchToScreen(R.id.screen_wait);
            startActivityForResult(intent, RC_INVITATION_INBOX);
            break;
        case R.id.button_accept_popup_invitation:
            if (!mAlreadyLoadedState)
                loadFromCloud();
            // user wants to accept the invitation shown on the invitation popup
            // (the one we got through the OnInvitationReceivedListener).
            acceptInviteToRoom(mIncomingInvitationId);
            mIncomingInvitationId = null;
            break;
        case R.id.button_quick_game:
            if (!mAlreadyLoadedState)
                loadFromCloud();
            // user wants to play against a random opponent right now
            showDialog(GAME_GMS_AUTOMATCH_VARIANT_OPT);
            break;
        case R.id.button_leaderboard:
            startActivityForResult(Games.Leaderboards.getAllLeaderboardsIntent(getApiClient()), RC_UNUSED);
            break;
        case R.id.button_achive:
            startActivityForResult(Games.Achievements.getAchievementsIntent(getApiClient()), RC_UNUSED);
            break;
        }
    }

    @Override
    public void onSignInFailed() {
        if (!isSinglePlayer) {
            // Switch to screen sigh in
            switchToScreen(R.id.screen_sign_in);
        }
    }

    @Override
    public void onSignInSucceeded() {
        if (!isSinglePlayer) {
            Games.Invitations.registerInvitationListener(getApiClient(), this);
            if (getInvitationId() != null) {
                acceptInviteToRoom(getInvitationId());
                return;
            }

            if (!mAlreadyLoadedState) {
                loadFromCloud();
            }
            displayPlayerNameScoreRank();
            // Switch to screen buttons (random, invite, invite inbox)
            switchToMainScreen();
        }
    }

    void displayPlayerNameScoreRank() {
        ((TextView) findViewById(R.id.playerDispNameId)).setText(getString(R.string.str_gms_hi) + " "
                + Games.Players.getCurrentPlayer(getApiClient()).getDisplayName() + "!");
        if (mSaveGame.getStatsFromName(ConstantsData.CH_KEY_PLAYED) != 0) {
            ((TextView) findViewById(R.id.playerDispScoreId)).setText(getString(R.string.str_gms_disp_rating)
                    + mSaveGame.getStatsFromName(ConstantsData.CH_KEY_RATIND) + " ("
                    + getString(R.string.str_gms_disp_win) + mSaveGame.getStatsFromName(ConstantsData.CH_KEY_WON)
                    + ", " + getString(R.string.str_gms_disp_draw)
                    + mSaveGame.getStatsFromName(ConstantsData.CH_KEY_DRAW) + ", "
                    + getString(R.string.str_gms_disp_loss) + mSaveGame.getStatsFromName(ConstantsData.CH_KEY_LOST)
                    + ")");
        } else {
            ((TextView) findViewById(R.id.playerDispScoreId)).setVisibility(View.GONE);
        }
    }

    void startQuickGame(int variant) {
        // quick-start a game with 1 randomly selected opponent
        final int MIN_OPPONENTS = 1, MAX_OPPONENTS = 1;
        Bundle autoMatchCriteria = RoomConfig.createAutoMatchCriteria(MIN_OPPONENTS, MAX_OPPONENTS, 0);
        RoomConfig.Builder rtmConfigBuilder = RoomConfig.builder(this);
        rtmConfigBuilder.setMessageReceivedListener(this);
        rtmConfigBuilder.setRoomStatusUpdateListener(this);
        rtmConfigBuilder.setAutoMatchCriteria(autoMatchCriteria);
        rtmConfigBuilder.setVariant(variant);
        switchToScreen(R.id.screen_wait);
        keepScreenOn();
        //resetGameVars();
        Games.RealTimeMultiplayer.create(getApiClient(), rtmConfigBuilder.build());
    }

    // Handle the result of the "Select players UI" we launched when the user clicked the
    // "Invite friends" button. We react by creating a room with those players.
    private void handleSelectPlayersResult(int response, Intent data, int variant) {
        if (response != Activity.RESULT_OK) {
            //Log.w(TAG, "*** select players UI cancelled, " + response);
            switchToMainScreen();
            return;
        }

        Log.d(TAG, "Select players UI succeeded.");

        // get the invitee list
        final ArrayList<String> invitees = data.getStringArrayListExtra(Games.EXTRA_PLAYER_IDS);
        //Log.d(TAG, "Invitee count: " + invitees.size());

        // get the automatch criteria
        Bundle autoMatchCriteria = null;
        int minAutoMatchPlayers = data.getIntExtra(Multiplayer.EXTRA_MIN_AUTOMATCH_PLAYERS, 0);
        int maxAutoMatchPlayers = data.getIntExtra(Multiplayer.EXTRA_MAX_AUTOMATCH_PLAYERS, 0);
        if (minAutoMatchPlayers > 0 || maxAutoMatchPlayers > 0) {
            autoMatchCriteria = RoomConfig.createAutoMatchCriteria(minAutoMatchPlayers, maxAutoMatchPlayers, 0);
            //Log.d(TAG, "Automatch criteria: " + autoMatchCriteria);
        }

        // create the room
        Log.d(TAG, "Creating room...");
        RoomConfig.Builder rtmConfigBuilder = RoomConfig.builder(this);
        rtmConfigBuilder.addPlayersToInvite(invitees);
        rtmConfigBuilder.setMessageReceivedListener(this);
        rtmConfigBuilder.setRoomStatusUpdateListener(this);
        if (autoMatchCriteria != null) {
            rtmConfigBuilder.setAutoMatchCriteria(autoMatchCriteria);
        }
        rtmConfigBuilder.setVariant(variant);
        switchToScreen(R.id.screen_wait);
        keepScreenOn();
        resetGameVars();
        resetGameBoolVars();
        Games.RealTimeMultiplayer.create(getApiClient(), rtmConfigBuilder.build());
        Log.d(TAG, "Room created, waiting for it to be ready...");
    }

    // Handle the result of the invitation inbox UI, where the player can pick an invitation
    // to accept. We react by accepting the selected invitation, if any.
    private void handleInvitationInboxResult(int response, Intent data) {
        if (response != Activity.RESULT_OK) {
            Log.w(TAG, "*** invitation inbox UI cancelled, " + response);
            switchToMainScreen();
            return;
        }

        Log.d(TAG, "Invitation inbox UI succeeded.");
        Invitation inv = data.getExtras().getParcelable(Multiplayer.EXTRA_INVITATION);

        // accept invitation
        acceptInviteToRoom(inv.getInvitationId());
    }

    // Accept the given invitation.
    void acceptInviteToRoom(String invId) {
        // accept the invitation
        Log.d(TAG, "Accepting invitation: " + invId);
        RoomConfig.Builder roomConfigBuilder = RoomConfig.builder(this);
        roomConfigBuilder.setInvitationIdToAccept(invId).setMessageReceivedListener(this)
                .setRoomStatusUpdateListener(this);
        switchToScreen(R.id.screen_wait);
        keepScreenOn();
        resetGameVars();
        resetGameBoolVars();
        Games.RealTimeMultiplayer.join(getApiClient(), roomConfigBuilder.build());
    }

    // Handle back key to make sure we cleanly leave a game if we are in the middle of one
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent e) {
        if (keyCode == KeyEvent.KEYCODE_BACK && mCurScreen == R.id.screen_game) {
            if (!isLeaveRoom && isMatch)
                showDialog(GAME_GMS_EXIT);
            return true;
        }
        return super.onKeyDown(keyCode, e);
    }

    // Leave the room.
    void leaveRoom() {
        isLeaveRoom = true;
        stopKeepingScreenOn();
        if (mRoomId != null) {
            Games.RealTimeMultiplayer.leave(getApiClient(), this, mRoomId);
            mRoomId = null;
            switchToScreen(R.id.screen_wait);
        } else {
            switchToMainScreen();
        }
    }

    // Show the waiting room UI to track the progress of other players as they enter the
    // room and get connected.
    void showWaitingRoom(Room room) {
        // minimum number of players required for our game
        // For simplicity, we require everyone to join the game before we start it
        // (this is signaled by Integer.MAX_VALUE).
        final int MIN_PLAYERS = Integer.MAX_VALUE;
        Intent i = Games.RealTimeMultiplayer.getWaitingRoomIntent(getApiClient(), room, MIN_PLAYERS);

        // show waiting room UI
        startActivityForResult(i, RC_WAITING_ROOM);
    }

    @Override
    public void onInvitationReceived(Invitation invitation) {
        // We got an invitation to play a game! So, store it in
        // mIncomingInvitationId
        // and show the popup on the screen.
        mIncomingInvitationId = invitation.getInvitationId();
        ((TextView) findViewById(R.id.incoming_invitation_text))
                .setText(invitation.getInviter().getDisplayName() + " " + getString(R.string.is_inviting_you));
        switchToScreen(mCurScreen); // This will show the invitation popup

    }

    @Override
    public void onInvitationRemoved(String arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void onJoinedRoom(int statusCode, Room room) {
        Log.d(TAG, "onJoinedRoom(" + statusCode + ", " + room + ")");
        if (statusCode != GamesStatusCodes.STATUS_OK) {
            Log.e(TAG, "*** Error: onRoomConnected, status " + statusCode);
            showGameError();
            return;
        }
        // show the waiting room UI
        showWaitingRoom(room);
    }

    // Called when we've successfully left the room (this happens a result of voluntarily leaving
    // via a call to leaveRoom(). If we get disconnected, we get onDisconnectedFromRoom()).
    @Override
    public void onLeftRoom(int statusCode, String roomId) {
        // we have left the room; return to main screen.
        Log.d(TAG, "onLeftRoom, code " + statusCode);
        switchToMainScreen();
    }

    // Called when room is fully connected.
    @Override
    public void onRoomConnected(int statusCode, Room room) {
        Log.d(TAG, "onRoomConnected(" + statusCode + ", " + room + ")");
        if (statusCode != GamesStatusCodes.STATUS_OK) {
            Log.e(TAG, "*** Error: onRoomConnected, status " + statusCode);
            showGameError();
            return;
        }
        updateRoom(room);
    }

    // Called when room has been created
    @Override
    public void onRoomCreated(int statusCode, Room room) {
        Log.d(TAG, "onRoomCreated(" + statusCode + ", " + room + ")");
        if (statusCode != GamesStatusCodes.STATUS_OK) {
            Log.e(TAG, "*** Error: onRoomCreated, status " + statusCode);
            showGameError();
            return;
        }

        // show the waiting room UI
        showWaitingRoom(room);
    }

    @Override
    public void onConnectedToRoom(Room room) {
        Log.d(TAG, "onConnectedToRoom.");

        // get room ID, participants and my ID:
        mRoomId = room.getRoomId();
        mParticipants = room.getParticipants();
        mMyId = room.getParticipantId(Games.Players.getCurrentPlayerId(getApiClient()));

        // print out the list of participants (for debug purposes)
        //Log.d(TAG, "Room ID: " + mRoomId);
        //Log.d(TAG, "My ID " + mMyId);
        //Log.d(TAG, "<< CONNECTED TO ROOM>>");
        gmsGameVariantNumber = room.getVariant();

        if (isServer()) {
            imFirstType = -1;
            sendFirstTypeForStart();
        }
        sendMyStats();
    }

    // Called when we get disconnected from the room. We return to the main screen.
    @Override
    public void onDisconnectedFromRoom(Room room) {
        mRoomId = null;
        if (opponentLeave) {
            Toast.makeText(this, getString(R.string.str_gms_leave_opponent_toast), Toast.LENGTH_SHORT).show();
            leaveRoom();
        } else
            showGameError();

    }

    // Show error message about game being cancelled and return to main screen.
    void showGameError() {
        //showAlert(getString(R.string.error), getString(R.string.game_problem));
        switchToMainScreen();
    }

    void updateRoom(Room room) {
        if (room != null) {
            mParticipants = room.getParticipants();
        }
    }

    /*
     * GAME LOGIC
     */

    // Reset game variables in preparation for a new game.
    void resetGameVars() {
        mParticipantScore.clear();
        mFinishedParticipants.clear();
    }

    void resetGameBoolVars() {
        imReady = false;
        opponentReady = false;
        isOpponentResign = false;
        isOpponentTimeOut = false;
    }

    // Current state of the game:
    int mSecondsLeft = -1; // how long until the game ends (seconds)
    int GAME_DURATION = -1;

    // Start the gameplay phase of the game.
    void startGame(boolean multiplayer, int variant) {
        resetGameVars();

        switch (variant) {
        case ConstantsData.GAME_VARIANT_LONG:
            mSecondsLeft = -1;
            GAME_DURATION = -1;
            ((TextView) findViewById(R.id.countdown)).setText("0:00");
            break;
        case ConstantsData.GAME_VARIANT_1MIN:
            mSecondsLeft = ConstantsData.GAME_DURATION_1MIN;
            GAME_DURATION = ConstantsData.GAME_DURATION_1MIN;
            break;
        case ConstantsData.GAME_VARIANT_2MIN:
            mSecondsLeft = ConstantsData.GAME_DURATION_2MIN;
            GAME_DURATION = ConstantsData.GAME_DURATION_2MIN;
            break;
        case ConstantsData.GAME_VARIANT_3MIN:
            mSecondsLeft = ConstantsData.GAME_DURATION_3MIN;
            GAME_DURATION = ConstantsData.GAME_DURATION_3MIN;
            break;
        default:
            break;
        }

        isLeaveRoom = false;
        mMultiplayer = multiplayer;

        switchToScreen(R.id.screen_game);
        startMultiplayerGameMode(imFirstType);

        if (mSecondsLeft > 0) {
            mHandler = new Handler();
            myRunnable = new Runnable() {
                @Override
                public void run() {
                    if (mSecondsLeft <= 0)
                        return;
                    gameTick();
                    mHandler.postDelayed(this, 1000);
                }
            };
            // run the gameTick() method every second to update the game.           
            mHandler.postDelayed(myRunnable, 1000);
        }
        resetGameBoolVars();
    }

    // Game tick -- update countdown, check if game ended.
    void gameTick() {
        if (mSecondsLeft > 0) {
            if (myTurn)
                --mSecondsLeft;
            else
                mSecondsLeft = GAME_DURATION;

            if (mSecondsLeft <= 10)
                ((TextView) findViewById(R.id.countdown)).setTextColor(Color.RED);
            else
                ((TextView) findViewById(R.id.countdown)).setTextColor(Color.WHITE);
        }
        // update countdown
        ((TextView) findViewById(R.id.countdown)).setText(getCountDownText(mSecondsLeft));

        if (mSecondsLeft <= 0) {
            // finish game
            timeOutGMSGame();
        }
    }

    String getCountDownText(int shownTime) {
        String result = null;
        if (shownTime == 180)
            result = ConstantsData.COUNTDOWN_TIMER_TEXT_180;
        else if (shownTime < 180 && shownTime >= 130)
            result = ConstantsData.COUNTDOWN_TIMER_TEXT_160 + Integer.toString(shownTime - 120);
        else if (shownTime < 130 && shownTime >= 120)
            result = ConstantsData.COUNTDOWN_TIMER_TEXT_130 + Integer.toString(shownTime - 120);
        else if (shownTime == 120)
            result = ConstantsData.COUNTDOWN_TIMER_TEXT_120;
        else if (shownTime < 120 && shownTime >= 70)
            result = ConstantsData.COUNTDOWN_TIMER_TEXT_100 + Integer.toString(shownTime - 60);
        else if (shownTime < 70 && shownTime >= 60)
            result = ConstantsData.COUNTDOWN_TIMER_TEXT_70 + Integer.toString(shownTime - 60);
        else if (shownTime < 60 && shownTime >= 10)
            result = ConstantsData.COUNTDOWN_TIMER_TEXT_60 + Integer.toString(shownTime);
        else if (shownTime < 10)
            result = ConstantsData.COUNTDOWN_TIMER_TEXT_9 + Integer.toString(shownTime);
        return result;
    }

    /*
     * COMMUNICATIONS SECTION. Methods that implement the game's network
     * protocol.
     */

    // Score of other participants. We update this as we receive their scores
    // from the network.
    Map<String, Integer> mParticipantScore = new HashMap<String, Integer>();

    // Participants who sent us their final score.
    Set<String> mFinishedParticipants = new HashSet<String>();

    // Called when we receive a real-time message from the network.
    // Messages in our game are made up of 2 bytes: the first one is 'F' or 'U'
    // indicating
    // whether it's a final or interim score. The second byte is the score.
    // There is also the
    // 'S' message, which indicates that the game should start.
    @Override
    public void onRealTimeMessageReceived(RealTimeMessage rtm) {
        byte[] buf = rtm.getMessageData();
        String sender = rtm.getSenderParticipantId();
        //Log.d(TAG, "Message received: " + (char) buf[0] + "/" + (int) buf[1]);

        if (buf[0] == 'S') {
            if ((int) buf[1] == 1)
                imFirstType = 0;
            else
                imFirstType = 1;
        }

        if (buf[0] == 'F') {
            if (imReady) {
                startGame(true, gmsGameVariantNumber);
            } else {
                opponentReady = true;
            }
        }

        if (buf[0] == 'M') {
            handleGMSClick((int) buf[1], (int) buf[2]);
        }

        if (buf[0] == 'D') {
            showDialog(GAME_GMS_DRAW_ASK);
        }

        if (buf[0] == '=') {
            ctrl.acceptDrawGmsGame();
            Toast.makeText(this, getString(R.string.draw_agree), Toast.LENGTH_SHORT).show();
        }

        if (buf[0] == '!') {
            Toast.makeText(this, "Not draw!", Toast.LENGTH_SHORT).show();
        }

        if (buf[0] == 'R') {
            isOpponentResign = true;
            ctrl.resignGame();
            if (imFirstType == 0)
                Toast.makeText(this, getString(R.string.resign_black), Toast.LENGTH_SHORT).show();
            else
                Toast.makeText(this, getString(R.string.resign_white), Toast.LENGTH_SHORT).show();
        }
        if (buf[0] == 'T') {
            isOpponentTimeOut = true;
            ctrl.resignGame();
            if (imFirstType == 0)
                Toast.makeText(this, getString(R.string.gms_white_win_time), Toast.LENGTH_SHORT).show();
            else
                Toast.makeText(this, getString(R.string.gms_black_win_time), Toast.LENGTH_SHORT).show();
        }
        if (buf.length > 10) {
            if (buf[2] == 's' && buf[3] == 't' && buf[4] == 'a' && buf[5] == 't') {
                try {
                    //sendMyStats();
                    loadOppStatsFromJson(new String(buf));
                } catch (Exception e) {
                    Toast.makeText(this, "Error load opponent stats", Toast.LENGTH_SHORT).show();
                }
            }
        }
    }

    /**
     * Broadcast move square.
     * 
     * @param sq         move selected square to this position/square  (to)       
     *           
     * @param selSquare  selected square (from)
     *            
     */
    void broadcastMove(int sq, int selSquare) {
        myTurn = false;
        // Message buffer for sending messages
        byte[] mMsgBuf = new byte[3];

        if (!mMultiplayer)
            return; // playing single-player mode

        // First byte in message indicates whether it's a final score or not
        mMsgBuf[0] = (byte) 'M';
        mMsgBuf[1] = (byte) sq;
        mMsgBuf[2] = (byte) selSquare;

        // Send to every other participant.
        for (Participant p : mParticipants) {
            if (p.getParticipantId().equals(mMyId))
                continue;
            if (p.getStatus() != Participant.STATUS_JOINED)
                continue;
            Games.RealTimeMultiplayer.sendReliableMessage(getApiClient(), this, mMsgBuf, mRoomId,
                    p.getParticipantId());
        }
    }

    private final void handleGMSClick(int sq, int selSquare) {
        myTurn = true;
        Move m = cb.mousePressedGMS2(sq, selSquare);
        if (m != null) {
            ctrl.makeHumanMoveGMS(m, true);
        }
        setEgtbHints(cb.getSelectedSquare());
    }

    /**
     * if the current player is server, it generates a random number (0=WHITE or 1=BLACK) and sends an opponent
     */
    void sendFirstTypeForStart() {
        if (imFirstType == -1) {
            byte[] mStartMsg = new byte[2];
            mStartMsg[0] = (byte) 'S';
            imFirstType = new Random().nextInt(2);
            mStartMsg[1] = (byte) imFirstType;
            for (Participant p : mParticipants) {
                if (p.getParticipantId().equals(mMyId))
                    continue;
                if (p.getStatus() != Participant.STATUS_JOINED)
                    continue;
                Games.RealTimeMultiplayer.sendReliableMessage(getApiClient(), this, mStartMsg, mRoomId,
                        p.getParticipantId());
            }
        }
    }

    void sendImReady() {
        byte[] mStartMsg = new byte[1];
        mStartMsg[0] = (byte) 'F';
        for (Participant p : mParticipants) {
            if (p.getParticipantId().equals(mMyId))
                continue;
            if (p.getStatus() != Participant.STATUS_JOINED)
                continue;
            Games.RealTimeMultiplayer.sendReliableMessage(getApiClient(), this, mStartMsg, mRoomId,
                    p.getParticipantId());
        }
    }

    void sendAskDrawIsPossible() {
        byte[] mStartMsg = new byte[1];
        mStartMsg[0] = (byte) 'D';
        for (Participant p : mParticipants) {
            if (p.getParticipantId().equals(mMyId))
                continue;
            if (p.getStatus() != Participant.STATUS_JOINED)
                continue;
            Games.RealTimeMultiplayer.sendReliableMessage(getApiClient(), this, mStartMsg, mRoomId,
                    p.getParticipantId());
        }
    }

    void sendAnswerDrawIsPossible(boolean answer) {
        byte[] mStartMsg = new byte[1];
        if (answer)
            mStartMsg[0] = (byte) '=';
        else
            mStartMsg[0] = (byte) '!';
        for (Participant p : mParticipants) {
            if (p.getParticipantId().equals(mMyId))
                continue;
            if (p.getStatus() != Participant.STATUS_JOINED)
                continue;
            Games.RealTimeMultiplayer.sendReliableMessage(getApiClient(), this, mStartMsg, mRoomId,
                    p.getParticipantId());
        }
    }

    void sendResignGame() {
        byte[] mStartMsg = new byte[1];
        mStartMsg[0] = (byte) 'R';
        for (Participant p : mParticipants) {
            if (p.getParticipantId().equals(mMyId))
                continue;
            if (p.getStatus() != Participant.STATUS_JOINED)
                continue;
            Games.RealTimeMultiplayer.sendReliableMessage(getApiClient(), this, mStartMsg, mRoomId,
                    p.getParticipantId());
        }
    }

    /**
     * Sending network packet if the current player ran out of time (GMS mode).
     */
    void sendTimeOutGame() {
        byte[] mStartMsg = new byte[1];
        mStartMsg[0] = (byte) 'T';
        for (Participant p : mParticipants) {
            if (p.getParticipantId().equals(mMyId))
                continue;
            if (p.getStatus() != Participant.STATUS_JOINED)
                continue;
            Games.RealTimeMultiplayer.sendReliableMessage(getApiClient(), this, mStartMsg, mRoomId,
                    p.getParticipantId());
        }
    }

    /**
     * Send current player stats to other player (GMS mode).
     */
    void sendMyStats() {
        mSaveGame.setUserName(Games.Players.getCurrentPlayer(getApiClient()).getDisplayName());
        byte[] mStartMsg = mSaveGame.toBytes();
        for (Participant p : mParticipants) {
            if (p.getParticipantId().equals(mMyId))
                continue;
            if (p.getStatus() != Participant.STATUS_JOINED)
                continue;
            Games.RealTimeMultiplayer.sendReliableMessage(getApiClient(), this, mStartMsg, mRoomId,
                    p.getParticipantId());
        }
    }
    /*
     * UI SECTION. Methods that implement the game's UI.
     */

    // This array lists all the individual screens our game has.

    final static int[] CLICKABLES = { R.id.button_accept_popup_invitation, R.id.button_invite_players,
            R.id.button_quick_game, R.id.button_see_invitations, R.id.button_sign_in, R.id.button_sign_out,
            R.id.button_leaderboard, R.id.button_achive };

    final static int[] SCREENS = { R.id.screen_game, R.id.screen_main, R.id.screen_sign_in, R.id.screen_wait };
    int mCurScreen = -1;

    void switchToScreen(int screenId) {
        // make the requested screen visible; hide all others.
        for (int id : SCREENS) {
            findViewById(id).setVisibility(screenId == id ? View.VISIBLE : View.GONE);
        }

        mCurScreen = screenId;
        // should we show the invitation popup?
        boolean showInvPopup;
        if (mIncomingInvitationId == null) {
            // no invitation, so no popup
            showInvPopup = false;
        } else if (mMultiplayer) {
            // if in multiplayer, only show invitation on main screen
            showInvPopup = (mCurScreen == R.id.screen_main);
        } else {
            // single-player: show on main screen and gameplay screen
            showInvPopup = (mCurScreen == R.id.screen_main || mCurScreen == R.id.screen_game);
        }
        findViewById(R.id.invitation_popup).setVisibility(showInvPopup ? View.VISIBLE : View.GONE);
    }

    void switchToMainScreen() {
        switchToScreen(isSignedIn() ? R.id.screen_main : R.id.screen_sign_in);
    }

    void showAdsGMS() {
        // Begin loading your 
        interstitial = new InterstitialAd(this);
        interstitial.setAdUnitId(ConstantsData.INTERSTITIAL_APPID);
        //adRequest = new AdRequest.Builder().build();
        //interstitial.loadAd(adRequest);
        // Set Ad Listener to use the callbacks below
        //interstitialsetAdListener(this);  
        // End AdMob
        // AdMob
        //if (synth.getId() % 2 == 0) {
        //   if(interstitial.isLoaded())
        //      interstitial.show();
        //}
    }

    // Sets the flag to keep this screen on. It's recommended to do that during
    // the
    // handshake when setting up a game, because if the screen turns off, the
    // game will be
    // cancelled.
    void keepScreenOn() {
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    }

    // Clears the flag that keeps the screen on.
    void stopKeepingScreenOn() {
        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    }

    @Override
    public void onStart() {
        if (!isSinglePlayer) {
            switchToScreen(R.id.screen_wait);
            mClient.connect();
        }
        super.onStart();
    }

    @Override
    public void onStop() {
        if (!isSinglePlayer) {
            leaveRoom();
            switchToScreen(R.id.screen_wait);
        }
        super.onStop();
    }

    @Override
    public void onRealTimeMessageSent(int statusCode, int tokenId, String recipientParticipantId) {
        // TODO Auto-generated method stub

    }

    private boolean isServer() {
        for (Participant p : mParticipants) {
            String pid = p.getParticipantId();
            if (pid.equals(mMyId)) {
                continue;
            }
            if (pid.compareTo(mMyId) < 0)
                return false;
        }
        return true;
    }

    private void storeScoreToLeaderBoard(long score) {
        Games.Leaderboards.submitScore(getApiClient(), getString(R.string.leaderboard_score), score);
    }

    private void storeWinnersToLeaderBoard(long win) {
        Games.Leaderboards.submitScore(getApiClient(), getString(R.string.leaderboard_winners), win);
    }

    final static int[] ACHIEVEMENT = { R.string.achievement_1200, R.string.achievement_1400,
            R.string.achievement_1600, R.string.achievement_1800, R.string.achievement_2000,
            R.string.achievement_2200, R.string.achievement_2400, R.string.achievement_2500,
            R.string.achievement_2500_GM, R.string.achievement_2600_PCM_1, R.string.achievement_2700_PCM_2,
            R.string.achievement_2800_CM };

    void unlockAchievement(int rating) {
        int index = ConstantsData.getIndexFromCheckedRange(rating);
        Games.Achievements.unlock(getApiClient(), getString(ACHIEVEMENT[index]));
    }

    private final Dialog gameGmsModeDialog() {
        final CharSequence[] items = { getString(R.string.option_resign_game), getString(R.string.option_draw),
                getString(R.string.load_save_menu) };
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.select_game_mode);
        builder.setItems(items, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int item) {
                int gameModeType = -1;
                /* only flip site in case the player was specified resp. changed */
                boolean flipSite = false;
                switch (item) {
                case 0:
                    resighGMSGame();
                    break;
                case 1:
                    if (!isLeaveRoom) {
                        if (ctrl.humansTurn()) {
                            if (ctrl.claimDrawIfPossible()) {
                                ctrl.stopPonder();
                            } else {
                                Toast.makeText(getApplicationContext(), R.string.offer_draw, Toast.LENGTH_SHORT)
                                        .show();
                            }
                        }
                        sendAskDrawIsPossible();
                    }
                    break;
                case 2:
                    removeDialog(FILE_MENU_DIALOG);
                    showDialog(FILE_MENU_DIALOG);
                    break;
                default:
                    break;
                }
                dialog.dismiss();
            }
        });
        AlertDialog alert = builder.create();
        return alert;
    }

    private void resighGMSGame() {
        if (!isLeaveRoom) {
            if (ctrl.humansTurn()) {
                ctrl.resignGame();
                sendResignGame();
            }
        }
    }

    private void timeOutGMSGame() {
        if (!isLeaveRoom) {
            if (ctrl.humansTurn()) {
                ctrl.resignGame();
                sendTimeOutGame();
            }
        }
    }

    private final Dialog newGmsGameDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.option_new_game);
        builder.setMessage(R.string.start_new_game);
        builder.setPositiveButton(R.string.yes, new Dialog.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                if (imFirstType == 0)
                    imFirstType = 1;
                else
                    imFirstType = 0;

                if (!opponentReady && !isLeaveRoom) {
                    imReady = true;
                    sendImReady();
                    Toast.makeText(getApplicationContext(), getString(R.string.draw_gms_try_again),
                            Toast.LENGTH_SHORT).show();
                } else {
                    if (!isLeaveRoom) {
                        sendImReady();
                        startGame(true, gmsGameVariantNumber);
                    }
                }
            }
        });
        builder.setNeutralButton(R.string.no, new Dialog.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                leaveRoom();
            }
        });
        return builder.create();
    }

    private final Dialog drawGmsGameDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.draw_gms_option_title);
        builder.setMessage(R.string.draw_gms_option_msg);
        builder.setPositiveButton(R.string.yes, new Dialog.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                if (!isLeaveRoom) {
                    sendAnswerDrawIsPossible(true);
                    ctrl.acceptDrawGmsGame();
                }
            }
        });
        builder.setNeutralButton(R.string.no, new Dialog.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                if (!isLeaveRoom)
                    sendAnswerDrawIsPossible(false);
            }
        });
        return builder.create();
    }

    private final Dialog gameGmsAutoMatchVariantDialog() {
        final CharSequence[] items = { getString(R.string.gms_variant_opt_long_game),
                getString(R.string.gms_variant_opt_1min_game), getString(R.string.gms_variant_opt_2min_game),
                getString(R.string.gms_variant_opt_3min_game) };
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.gms_variant_opt_title);
        builder.setItems(items, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int item) {
                int gameModeType = -1;
                /* only flip site in case the player was specified resp. changed */
                boolean flipSite = false;
                switch (item) {
                case 0:
                    gmsGameVariantNumber = ConstantsData.GAME_VARIANT_LONG;
                    startQuickGame(ConstantsData.GAME_VARIANT_LONG);
                    break;
                case 1:
                    gmsGameVariantNumber = ConstantsData.GAME_VARIANT_1MIN;
                    startQuickGame(ConstantsData.GAME_VARIANT_1MIN);
                    break;
                case 2:
                    gmsGameVariantNumber = ConstantsData.GAME_VARIANT_2MIN;
                    startQuickGame(ConstantsData.GAME_VARIANT_2MIN);
                    break;
                case 3:
                    gmsGameVariantNumber = ConstantsData.GAME_VARIANT_3MIN;
                    startQuickGame(ConstantsData.GAME_VARIANT_3MIN);
                    break;
                default:
                    break;
                }
                dialog.dismiss();
            }
        });
        AlertDialog alert = builder.create();
        return alert;
    }

    private final Dialog exitGmsGameDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.gms_exit_game_dialog_title);
        builder.setMessage(R.string.gms_exit_game_dialog_msg);
        builder.setPositiveButton(R.string.yes, new Dialog.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                resighGMSGame();
                leaveRoom();
            }
        });
        builder.setNeutralButton(R.string.no, new Dialog.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
            }
        });
        return builder.create();
    }

    private final Dialog gameGmsInviteVariantDialog() {
        final CharSequence[] items = { getString(R.string.gms_variant_opt_long_game),
                getString(R.string.gms_variant_opt_1min_game), getString(R.string.gms_variant_opt_2min_game),
                getString(R.string.gms_variant_opt_3min_game) };
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.gms_variant_opt_title);
        builder.setItems(items, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int item) {
                int gameModeType = -1;
                /* only flip site in case the player was specified resp. changed */
                boolean flipSite = false;
                switch (item) {
                case 0:
                    gmsGameVariantNumber = ConstantsData.GAME_VARIANT_LONG;
                    invateGMSPlayers();
                    break;
                case 1:
                    gmsGameVariantNumber = ConstantsData.GAME_VARIANT_1MIN;
                    invateGMSPlayers();
                    break;
                case 2:
                    gmsGameVariantNumber = ConstantsData.GAME_VARIANT_2MIN;
                    invateGMSPlayers();
                    break;
                case 3:
                    gmsGameVariantNumber = ConstantsData.GAME_VARIANT_3MIN;
                    invateGMSPlayers();
                    break;
                default:
                    break;
                }
                dialog.dismiss();
            }
        });
        AlertDialog alert = builder.create();
        return alert;
    }

    private void invateGMSPlayers() {
        // show list of invitable players
        Intent intent = Games.RealTimeMultiplayer.getSelectOpponentsIntent(getApiClient(), 1, 1);
        switchToScreen(R.id.screen_wait);
        startActivityForResult(intent, RC_SELECT_PLAYERS);
    }

    /**
     * Cloud Save section
     */
    private void loadLocal() {
        SharedPreferences sp = getSharedPreferences("gameStateGMS", Context.MODE_PRIVATE);
        mSaveGame = new SaveGame(sp, "gameStateGMS");
        String json = sp.getString("gameStateGMS", "");
        if (json == null || json.trim().equals(""))
            mAlreadyLocalState = false;
        else
            mAlreadyLocalState = true;
    }

    private void saveLocal() {
        SharedPreferences sp = getSharedPreferences("gameStateGMS", Context.MODE_PRIVATE);
        if (getApiClient().isConnected())
            mSaveGame.setUserName(Games.Players.getCurrentPlayer(getApiClient()).getDisplayName());
        if (mClient.isConnected())
            mSaveGame.setUserName(Games.Players.getCurrentPlayer(getApiClient()).getDisplayName());
        mSaveGame.save(sp, "gameStateGMS");
    }

    ResultCallback<AppStateManager.StateResult> mResultCallback = new ResultCallback<AppStateManager.StateResult>() {
        @Override
        public void onResult(AppStateManager.StateResult result) {
            AppStateManager.StateConflictResult conflictResult = result.getConflictResult();
            AppStateManager.StateLoadedResult loadedResult = result.getLoadedResult();
            if (loadedResult != null) {
                processStateLoaded(loadedResult);
            } else if (conflictResult != null) {
                processStateConflict(conflictResult);
            }
        }
    };

    void loadFromCloud() {
        AppStateManager.load(mClient, OUR_STATE_KEY).setResultCallback(mResultCallback);
    }

    void saveToCloud() {
        if (mClient.isConnected()) {
            mSaveGame.setUserName(Games.Players.getCurrentPlayer(getApiClient()).getDisplayName());
            AppStateManager.update(mClient, OUR_STATE_KEY, mSaveGame.toBytes());
            //Toast.makeText(getApplicationContext(), "     1: " + new String(mSaveGame.toBytes()), Toast.LENGTH_SHORT).show();
        } else {

        }

        // Note: this is a fire-and-forget call. It will NOT trigger a call to any callbacks!
    }

    private void processStateConflict(AppStateManager.StateConflictResult result) {
        // Need to resolve conflict between the two states.
        // We do that by taking the union of the two sets of cleared levels,
        // which means preserving the maximum star rating of each cleared
        // level:
        byte[] serverData = result.getServerData();
        byte[] localData = result.getLocalData();

        SaveGame localGame = new SaveGame(localData);
        SaveGame serverGame = new SaveGame(serverData);
        SaveGame resolvedGame = localGame.unionWith(serverGame);

        AppStateManager.resolve(mClient, result.getStateKey(), result.getResolvedVersion(), resolvedGame.toBytes())
                .setResultCallback(mResultCallback);
    }

    private void processStateLoaded(AppStateManager.StateLoadedResult result) {
        switch (result.getStatus().getStatusCode()) {
        case AppStateStatusCodes.STATUS_OK:
            // Data was successfully loaded from the cloud: merge with local data.
            mSaveGame = mSaveGame.unionWith(new SaveGame(result.getLocalData()));
            mAlreadyLoadedState = true;
            saveLocal();
            //Toast.makeText(getApplicationContext(), "   : " + new String(result.getLocalData()), Toast.LENGTH_SHORT).show();
            //hideAlertBar();
            break;
        case AppStateStatusCodes.STATUS_STATE_KEY_NOT_FOUND:
            // key not found means there is no saved data. To us, this is the same as
            // having empty data, so we treat this as a success.
            mAlreadyLoadedState = true;
            //hideAlertBar();
            break;
        case AppStateStatusCodes.STATUS_NETWORK_ERROR_NO_DATA:
            // can't reach cloud, and we have no local state. Warn user that
            // they may not see their existing progress, but any new progress won't be lost.
            //showAlertBar(R.string.no_data_warning);
            break;
        case AppStateStatusCodes.STATUS_NETWORK_ERROR_STALE_DATA:
            // can't reach cloud, but we have locally cached data.
            //showAlertBar(R.string.stale_data_warning);
            break;
        case AppStateStatusCodes.STATUS_CLIENT_RECONNECT_REQUIRED:
            // need to reconnect AppStateClient
            reconnectClient();
            break;
        default:
            // error
            //showAlertBar(R.string.load_error_warning);
            break;
        }

        updateRatingUi();
    }

    private void showAlertBar(int resId) {
        // ((TextView) findViewById(R.id.alert_bar)).setText(getString(resId));
        // ((TextView) findViewById(R.id.alert_bar)).setVisibility(View.VISIBLE);
    }

    private void hideAlertBar() {
        //((TextView) findViewById(R.id.alert_bar)).setVisibility(View.GONE);
    }

    /**
     * refresh Rating in game after match
     */
    private void updateRatingUi() {
        //todo
    }

    private void handleGMSMatchComplete(int result) {
        if (mHandler != null)
            mHandler.removeCallbacks(myRunnable);
        isMatch = false;
        if (mSaveGame.getStatsFromName(ConstantsData.CH_KEY_PLAYED) == 0) {
            unlockAchievement(0);
            loadFromCloud();
        }
        mSaveGame.setStatsFromResult(result, opponentRating, gameTypeMode);
        saveLocal();
        saveToCloud();
        sendMyStats();
        storeScoreToLeaderBoard(mSaveGame.getStatsFromName(ConstantsData.CH_KEY_RATIND));
        storeWinnersToLeaderBoard(mSaveGame.getStatsFromName(ConstantsData.CH_KEY_WON));
        unlockAchievement(mSaveGame.getStatsFromName(ConstantsData.CH_KEY_RATIND));
        showDialog(NEW_GMS_GAME_DIALOG);
    }

    public void loadOppStatsFromJson(String json) {
        //mOpponentStats.clear();
        if (json == null || json.trim().equals(""))
            return;

        try {
            JSONObject obj = new JSONObject(json);
            String format = obj.getString("version");
            if (!format.equals(ConstantsData.SERIAL_VERSION)) {
                throw new RuntimeException("Unexpected loot format " + format);
            }
            opponentName = obj.getString("username");

            JSONObject stats = obj.getJSONObject("stats");
            Iterator<?> iter = stats.keys();

            while (iter.hasNext()) {
                String statName = (String) iter.next();
                mOpponentStats.put(statName, stats.getInt(statName));
            }
            Integer r = 0;
            r = mOpponentStats.get("rating");
            opponentRating = r.intValue();
        } catch (JSONException ex) {
            ex.printStackTrace();
            throw new RuntimeException("Opponent stats data has a syntax error: " + json, ex);
        } catch (NumberFormatException ex) {
            ex.printStackTrace();
            throw new RuntimeException("Opponent stats has an invalid number in it: " + json, ex);
        }
    }
}