paulscode.android.mupen64plusae.game.GameFragment.java Source code

Java tutorial

Introduction

Here is the source code for paulscode.android.mupen64plusae.game.GameFragment.java

Source

/*
 * Mupen64PlusAE, an N64 emulator for the Android platform
 *
 * Copyright (C) 2013 Paul Lamb
 *
 * This file is part of Mupen64PlusAE.
 *
 * Mupen64PlusAE 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.
 *
 * Mupen64PlusAE 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 Mupen64PlusAE. If
 * not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: littleguy77
 */
package paulscode.android.mupen64plusae.game;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.drawable.BitmapDrawable;
import android.hardware.SensorManager;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Vibrator;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager.LayoutParams;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;

import com.bda.controller.Controller;

import org.mupen64plusae.v3.alpha.R;

import java.io.File;
import java.util.ArrayList;
import java.util.Calendar;

import paulscode.android.mupen64plusae.ActivityHelper;
import paulscode.android.mupen64plusae.DrawerDrawable;
import paulscode.android.mupen64plusae.GameSidebar;
import paulscode.android.mupen64plusae.GameSidebar.GameSidebarActionHandler;
import paulscode.android.mupen64plusae.dialog.ConfirmationDialog.PromptConfirmListener;
import paulscode.android.mupen64plusae.dialog.Popups;
import paulscode.android.mupen64plusae.dialog.Prompt;
import paulscode.android.mupen64plusae.dialog.Prompt.PromptIntegerListener;
import paulscode.android.mupen64plusae.hack.MogaHack;
import paulscode.android.mupen64plusae.input.PeripheralController;
import paulscode.android.mupen64plusae.input.SensorController;
import paulscode.android.mupen64plusae.input.TouchController;
import paulscode.android.mupen64plusae.input.map.VisibleTouchMap;
import paulscode.android.mupen64plusae.input.provider.AbstractProvider;
import paulscode.android.mupen64plusae.input.provider.AxisProvider;
import paulscode.android.mupen64plusae.input.provider.KeyProvider;
import paulscode.android.mupen64plusae.input.provider.KeyProvider.ImeFormula;
import paulscode.android.mupen64plusae.input.provider.MogaProvider;
import paulscode.android.mupen64plusae.jni.CoreFragment;
import paulscode.android.mupen64plusae.jni.CoreFragment.CoreEventListener;
import paulscode.android.mupen64plusae.jni.CoreService;
import paulscode.android.mupen64plusae.jni.NativeConstants;
import paulscode.android.mupen64plusae.persistent.AppData;
import paulscode.android.mupen64plusae.persistent.GamePrefs;
import paulscode.android.mupen64plusae.persistent.GlobalPrefs;
import paulscode.android.mupen64plusae.persistent.GlobalPrefs.PakType;
import paulscode.android.mupen64plusae.profile.ControllerProfile;
import paulscode.android.mupen64plusae.util.CountryCode;
import paulscode.android.mupen64plusae.util.RomDatabase;
import paulscode.android.mupen64plusae.util.RomDatabase.RomDetail;

//@formatter:off
/**
* (start)
*    |
* onCreate <-- (killed) <---------\
*    |                            |
* onStart  <-- onRestart <-----\  |
*    |                         |  |
* onResume <----------------\  |  |
*    |                      |  |  |
* [*onSurfaceCreated*]      |  |  |
*    |                      |  |  |
* [*onSurfaceChanged*]      |  |  |
*    |                      |  |  |
* [*onWindowFocusChanged*]  |  |  |
*    |                      |  |  |
* (running)                 |  |  |
*    |                      |  |  |
* [*onWindowFocusChanged*]  |  |  |
*    |                      |  |  |
* onPause ------------------/  |  |
*    |                         |  |
* [*onSurfaceDestroyed*]       |  |
*    |                         |  |
* onStop ----------------------/--/
*    |
* onDestroy
*    |
* (end)
*
*
* [*non-deterministic sequence*]
*
*
*/
//@formatter:on

public class GameFragment extends Fragment
        implements PromptConfirmListener, SurfaceHolder.Callback, GameSidebarActionHandler, CoreEventListener,
        View.OnTouchListener, ViewTreeObserver.OnWindowFocusChangeListener, View.OnGenericMotionListener {
    public interface OnGameActivityFinished {
        void onGameActivityFinished();
    }

    // Activity and views
    private GameOverlay mOverlay;
    private GameDrawerLayout mDrawerLayout;
    private GameSidebar mGameSidebar;

    // Input resources
    private VisibleTouchMap mTouchscreenMap;
    private KeyProvider mKeyProvider;
    private AxisProvider mAxisProvider;
    private Controller mMogaController;
    TouchController mTouchscreenController;
    private SensorController mSensorController;
    private int mLastTouchTime;
    private Handler mHandler;

    // args data
    private String mRomPath;
    private String mRomMd5;
    private String mRomCrc;
    private String mRomGoodName;
    private String mRomHeaderName = null;
    private byte mRomCountryCode = 0;
    private String mRomArtPath = null;
    private String mRomLegacySave = null;
    private boolean mDoRestart;

    // Lifecycle state tracking
    private boolean mIsResumed = false; // true if the activity is resumed
    private boolean mIsSurface = false; // true if the surface is available

    // App data and user preferences
    private AppData mAppData;
    private GlobalPrefs mGlobalPrefs;
    private GamePrefs mGamePrefs;
    private GameDataManager mGameDataManager;
    private boolean mFirstStart;
    private boolean mWaitingOnConfirmation = false;
    private boolean mShuttingDown = false;
    private boolean mDrawerOpenState = false;

    private static final String STATE_CORE_FRAGMENT = "STATE_CORE_FRAGMENT";
    private CoreFragment mCoreFragment = null;

    public static GameFragment newInstance(String romPath, String romMd5, String romCrc, String romHeaderName,
            byte romCountryCode, String romArtPath, String romGoodName, String romLegacySave, boolean doRestart) {
        GameFragment gameFragment = new GameFragment();
        Bundle args = new Bundle();

        args.putString(ActivityHelper.Keys.ROM_PATH, romPath);
        args.putString(ActivityHelper.Keys.ROM_MD5, romMd5);
        args.putString(ActivityHelper.Keys.ROM_CRC, romCrc);
        args.putString(ActivityHelper.Keys.ROM_HEADER_NAME, romHeaderName);
        args.putByte(ActivityHelper.Keys.ROM_COUNTRY_CODE, romCountryCode);
        args.putString(ActivityHelper.Keys.ROM_ART_PATH, romArtPath);
        args.putString(ActivityHelper.Keys.ROM_GOOD_NAME, romGoodName);
        args.putString(ActivityHelper.Keys.ROM_LEGACY_SAVE, romLegacySave);
        args.putBoolean(ActivityHelper.Keys.DO_RESTART, doRestart);
        gameFragment.setArguments(args);

        return gameFragment;
    }

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

        Log.i("GameFragment", "onCreateView");

        return inflater.inflate(R.layout.game_activity, container, false);
    }

    @Override
    // this method is only called once for this fragment
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // retain this fragment
        setRetainInstance(true);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mAppData = new AppData(getActivity());

        mMogaController = Controller.getInstance(getActivity());

        // Initialize the objects and data files interfacing to the emulator core
        final FragmentManager fm = getActivity().getSupportFragmentManager();
        mCoreFragment = (CoreFragment) fm.findFragmentByTag(STATE_CORE_FRAGMENT);

        if (mCoreFragment == null) {
            mCoreFragment = new CoreFragment();
            fm.beginTransaction().add(mCoreFragment, STATE_CORE_FRAGMENT).commit();
        }

        mCoreFragment.setCoreEventListener(this);

        // Get the args data
        mRomPath = getArguments().getString(ActivityHelper.Keys.ROM_PATH);
        mRomMd5 = getArguments().getString(ActivityHelper.Keys.ROM_MD5);
        mRomCrc = getArguments().getString(ActivityHelper.Keys.ROM_CRC);
        mRomHeaderName = getArguments().getString(ActivityHelper.Keys.ROM_HEADER_NAME);
        mRomCountryCode = getArguments().getByte(ActivityHelper.Keys.ROM_COUNTRY_CODE);
        mRomArtPath = getArguments().getString(ActivityHelper.Keys.ROM_ART_PATH);
        mRomGoodName = getArguments().getString(ActivityHelper.Keys.ROM_GOOD_NAME);
        mRomLegacySave = getArguments().getString(ActivityHelper.Keys.ROM_LEGACY_SAVE);
        mDoRestart = getArguments().getBoolean(ActivityHelper.Keys.DO_RESTART, false);
        if (TextUtils.isEmpty(mRomPath) || TextUtils.isEmpty(mRomMd5))
            throw new Error("ROM path and MD5 must be passed via the extras bundle when starting GameFragment");

        // Initialize MOGA controller API
        // TODO: Remove hack after MOGA SDK is fixed
        // mMogaController.init();
        MogaHack.init(mMogaController, getActivity());

        // Get app data and user preferences
        mGlobalPrefs = new GlobalPrefs(getActivity(), mAppData);

        //Allow volume keys to control media volume if they are not mapped

        if (!mGlobalPrefs.volKeysMappable && mGlobalPrefs.audioPlugin.enabled) {
            getActivity().setVolumeControlStream(AudioManager.STREAM_MUSIC);
        }

        mGamePrefs = new GamePrefs(getActivity(), mRomMd5, mRomCrc, mRomHeaderName, mRomGoodName,
                CountryCode.getCountryCode(mRomCountryCode).toString(), mAppData, mGlobalPrefs, mRomLegacySave);

        mGameDataManager = new GameDataManager(mGlobalPrefs, mGamePrefs, mGlobalPrefs.maxAutoSaves);
        mGameDataManager.makeDirs();
        mGameDataManager.moveFromLegacy();

        final Window window = getActivity().getWindow();

        // Enable full-screen mode
        window.setFlags(LayoutParams.FLAG_FULLSCREEN, LayoutParams.FLAG_FULLSCREEN);
        window.setFlags(LayoutParams.FLAG_LAYOUT_IN_SCREEN, LayoutParams.FLAG_LAYOUT_IN_SCREEN);

        // Keep screen from going to sleep
        window.setFlags(LayoutParams.FLAG_KEEP_SCREEN_ON, LayoutParams.FLAG_KEEP_SCREEN_ON);

        mFirstStart = true;

        // Lay out content and get the views
        SurfaceView surfaceView = (SurfaceView) getView().findViewById(R.id.gameSurface);

        mOverlay = (GameOverlay) getView().findViewById(R.id.gameOverlay);
        mDrawerLayout = (GameDrawerLayout) getView().findViewById(R.id.drawerLayout);
        mGameSidebar = (GameSidebar) getView().findViewById(R.id.gameSidebar);

        // Don't darken the game screen when the drawer is open
        mDrawerLayout.setScrimColor(0x0);
        mDrawerLayout.setSwipGestureEnabled(mGlobalPrefs.inGameMenuIsSwipGesture);
        mDrawerLayout.setBackgroundColor(0xFF000000);

        if (!TextUtils.isEmpty(mRomArtPath) && new File(mRomArtPath).exists())
            mGameSidebar.setImage(new BitmapDrawable(this.getResources(), mRomArtPath));

        mGameSidebar.setTitle(mRomGoodName);

        // Handle events from the side bar
        mGameSidebar.setActionHandler(this, R.menu.game_drawer);

        // Listen to game surface events (created, changed, destroyed)
        surfaceView.getHolder().addCallback(this);

        // Update the SurfaceView size
        surfaceView.getHolder().setFixedSize(mGamePrefs.videoRenderWidth, mGamePrefs.videoRenderHeight);
        final FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) surfaceView.getLayoutParams();
        params.width = Math.round(mGamePrefs.videoSurfaceWidth * (mGamePrefs.videoSurfaceZoom / 100.f));
        params.height = Math.round(mGamePrefs.videoSurfaceHeight * (mGamePrefs.videoSurfaceZoom / 100.f));
        params.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
        surfaceView.setLayoutParams(params);

        // Initialize the screen elements
        if (mGamePrefs.isTouchscreenEnabled || mGlobalPrefs.isFpsEnabled) {
            // The touch map and overlay are needed to display frame rate and/or controls
            mTouchscreenMap = new VisibleTouchMap(this.getResources());
            mTouchscreenMap.load(mGamePrefs.touchscreenSkin, mGamePrefs.touchscreenProfile,
                    mGlobalPrefs.isTouchscreenAnimated, mGlobalPrefs.isFpsEnabled, mGlobalPrefs.fpsXPosition,
                    mGlobalPrefs.fpsYPosition, mGlobalPrefs.touchscreenScale, mGlobalPrefs.touchscreenTransparency);
            mOverlay.initialize(mCoreFragment, mTouchscreenMap, !mGamePrefs.isTouchscreenHidden,
                    mGlobalPrefs.isFpsEnabled, mGamePrefs.isAnalogHiddenWhenSensor,
                    mGlobalPrefs.isTouchscreenAnimated);
        }

        // Initialize user interface devices
        initControllers(mOverlay);

        // Override the peripheral controllers' key provider, to add some extra
        // functionality
        mOverlay.setOnKeyListener(this);
        mOverlay.requestFocus();

        if (savedInstanceState == null) {
            // Show the drawer at the start and have it hide itself
            // automatically
            mDrawerLayout.openDrawer(GravityCompat.START);
            mDrawerLayout.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mDrawerLayout.closeDrawer(GravityCompat.START);
                }
            }, 1000);
        }

        if (mDrawerOpenState) {
            Log.e("CoreFragment", "Opening drawer");
            mDrawerLayout.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mCoreFragment.pauseEmulator();
                    mDrawerLayout.openDrawer(GravityCompat.START);
                }
            }, 1000);

        }

        mDrawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {

            @Override
            public void onDrawerClosed(View arg0) {
                if (!mShuttingDown) {
                    mCoreFragment.resumeEmulator();
                    mDrawerOpenState = false;
                }

            }

            @Override
            public void onDrawerOpened(View arg0) {
                mCoreFragment.pauseEmulator();
                ReloadAllMenus();
            }

            @Override
            public void onDrawerSlide(View arg0, float arg1) {

            }

            @Override
            public void onDrawerStateChanged(int newState) {

            }

        });

        // Check periodically for touch input to determine if we should
        // hide the controls
        mHandler = new Handler();
        Calendar calendar = Calendar.getInstance();
        mLastTouchTime = calendar.get(Calendar.SECOND);

        if (mGlobalPrefs.touchscreenAutoHideEnabled)
            mHandler.postDelayed(mLastTouchChecker, 500);

        //Callback for onWindowFocusChanged
        final ViewTreeObserver viewTreeObserver = getView().getViewTreeObserver();
        viewTreeObserver.addOnWindowFocusChangeListener(this);

        //Callback for onGenericMotion
        getView().setOnGenericMotionListener(this);
    }

    @Override
    public void onStart() {
        super.onStart();
        Log.i("GameFragment", "onStart");
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.i("GameFragment", "onResume");

        if (!mIsResumed) {
            mIsResumed = true;

            tryRunning();

            if (mSensorController != null) {
                mSensorController.onResume();
            }

            // Set the sidebar opacity
            mGameSidebar.setBackground(new DrawerDrawable(mGlobalPrefs.displayActionBarTransparency));

            mMogaController.onResume();
        }
    }

    @Override
    public void onStop() {
        super.onStop();

        Log.i("GameFragment", "onStop");
        mIsResumed = false;

        if (!mShuttingDown && mCoreFragment.hasServiceStarted()) {
            tryPausing();
        }

        if (mSensorController != null) {
            mSensorController.onPause();
        }

        mMogaController.onPause();
    }

    //This is only called once when fragment is destroyed due to rataining the state
    @Override
    public void onDestroy() {
        super.onDestroy();

        Log.i("GameFragment", "onDestroy");

        showSystemBars();

        mHandler.removeCallbacks(mLastTouchChecker);
        final FragmentManager fm = getActivity().getSupportFragmentManager();
        fm.beginTransaction().remove(mCoreFragment).commit();
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        // Only try to run; don't try to pause. User may just be touching the in-game menu.
        Log.i("GameFragment", "onWindowFocusChanged: " + hasFocus);
        if (hasFocus) {
            hideSystemBars();
        }

        //We don't want to do this every time the user uses a dialog,
        //only do it when the activity is first created.
        if (mFirstStart) {
            tryRunning();
            mFirstStart = false;
        }
    }

    @Override
    public void onPromptDialogClosed(int id, int which) {
        mCoreFragment.onPromptDialogClosed(id, which);
    }

    private void ReloadAllMenus() {
        //Reload currently selected speed setting
        final MenuItem toggleSpeedItem = mGameSidebar.getMenu().findItem(R.id.menuItem_toggle_speed);
        toggleSpeedItem.setTitle(this.getString(R.string.menuItem_toggleSpeed, mCoreFragment.getCurrentSpeed()));

        //Reload currently selected slot
        final MenuItem slotItem = mGameSidebar.getMenu().findItem(R.id.menuItem_set_slot);
        slotItem.setTitle(this.getString(R.string.menuItem_setSlot, mCoreFragment.getSlot()));

        final int resId = mCoreFragment.getFramelimiter() ? R.string.menuItem_enableFramelimiter
                : R.string.menuItem_disableFramelimiter;

        //Reload the menu with the new frame limiter setting
        final MenuItem frameLimiterItem = mGameSidebar.getMenu().findItem(R.id.menuItem_disable_frame_limiter);
        frameLimiterItem.setTitle(this.getString(resId));

        //Reload player pak settings
        UpdateControllerMenu(R.id.menuItem_player_one, mGamePrefs.isPlugged1, 1);
        UpdateControllerMenu(R.id.menuItem_player_two, mGamePrefs.isPlugged2, 2);
        UpdateControllerMenu(R.id.menuItem_player_three, mGamePrefs.isPlugged3, 3);
        UpdateControllerMenu(R.id.menuItem_player_four, mGamePrefs.isPlugged4, 4);

        mGameSidebar.reload();
    }

    private void UpdateControllerMenu(int menuItemId, boolean isPlugged, int playerNumber) {
        final MenuItem pakGroupItem = mGameSidebar.getMenu().findItem(R.id.menuItem_paks);

        if (mGameSidebar.getMenu().findItem(menuItemId) != null) {
            if (!isPlugged) {
                pakGroupItem.getSubMenu().removeItem(menuItemId);
            } else {
                final MenuItem playerItem = mGameSidebar.getMenu().findItem(menuItemId);
                playerItem.setTitleCondensed(
                        this.getString(mGlobalPrefs.getPakType(playerNumber).getResourceString()));
            }
        }
    }

    @Override
    public void onPromptFinished() {
        //In here we only reload things that are updated through prompts

        //reload menu item with new slot
        final MenuItem slotItem = mGameSidebar.getMenu().findItem(R.id.menuItem_set_slot);
        slotItem.setTitle(this.getString(R.string.menuItem_setSlot, mCoreFragment.getSlot()));

        //Reload the menu with the new speed
        final MenuItem toggleSpeedItem = mGameSidebar.getMenu().findItem(R.id.menuItem_toggle_speed);
        toggleSpeedItem.setTitle(this.getString(R.string.menuItem_toggleSpeed, mCoreFragment.getCurrentSpeed()));

        mGameSidebar.reload();
    }

    @Override
    public void onSaveLoad() {
        if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
            mDrawerLayout.closeDrawer(GravityCompat.START);
        }
    }

    @Override
    public void onGameSidebarAction(MenuItem menuItem) {
        switch (menuItem.getItemId()) {
        case R.id.menuItem_exit:
            mWaitingOnConfirmation = true;
            mCoreFragment.exit();
            break;
        case R.id.menuItem_toggle_speed:
            mCoreFragment.toggleSpeed();

            //Reload the menu with the new speed
            final MenuItem toggleSpeedItem = mGameSidebar.getMenu().findItem(R.id.menuItem_toggle_speed);
            toggleSpeedItem
                    .setTitle(this.getString(R.string.menuItem_toggleSpeed, mCoreFragment.getCurrentSpeed()));
            mGameSidebar.reload();
            break;
        case R.id.menuItem_set_speed:
            mCoreFragment.setCustomSpeedFromPrompt();
            break;
        case R.id.menuItem_screenshot:
            mCoreFragment.screenshot();
            break;
        case R.id.menuItem_set_slot:
            mCoreFragment.setSlotFromPrompt();
            break;
        case R.id.menuItem_slot_load:
            mCoreFragment.loadSlot();
            break;
        case R.id.menuItem_slot_save:
            mCoreFragment.saveSlot();

            if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
                mDrawerLayout.closeDrawer(GravityCompat.START);
            }
            break;
        case R.id.menuItem_file_load:
            mCoreFragment.loadFileFromPrompt();
            break;
        case R.id.menuItem_file_save:
            mCoreFragment.saveFileFromPrompt();
            break;
        case R.id.menuItem_file_load_auto_save:
            mCoreFragment.loadAutoSaveFromPrompt();
            break;
        case R.id.menuItem_disable_frame_limiter:
            mCoreFragment.toggleFramelimiter();

            final int resId = mCoreFragment.getFramelimiter() ? R.string.menuItem_enableFramelimiter
                    : R.string.menuItem_disableFramelimiter;

            //Reload the menu with the new speed
            final MenuItem frameLimiterItem = mGameSidebar.getMenu().findItem(R.id.menuItem_disable_frame_limiter);
            frameLimiterItem.setTitle(this.getString(resId));
            mGameSidebar.reload();
            break;
        case R.id.menuItem_player_one:
            setPakTypeFromPrompt(1);
            break;
        case R.id.menuItem_player_two:
            setPakTypeFromPrompt(2);
            break;
        case R.id.menuItem_player_three:
            setPakTypeFromPrompt(3);
            break;
        case R.id.menuItem_player_four:
            setPakTypeFromPrompt(4);
            break;
        case R.id.menuItem_setIme:
            final InputMethodManager imeManager = (InputMethodManager) getActivity()
                    .getSystemService(Context.INPUT_METHOD_SERVICE);
            if (imeManager != null)
                imeManager.showInputMethodPicker();
            break;
        case R.id.menuItem_reset:
            mWaitingOnConfirmation = true;
            mCoreFragment.restart();
            break;
        default:
        }
    }

    private CharSequence GetPlayerTextFromId(int playerId) {
        CharSequence title = null;

        switch (playerId) {
        case 1:
            title = this.getString(R.string.menuItem_player_one);
            break;
        case 2:
            title = this.getString(R.string.menuItem_player_two);
            break;
        case 3:
            title = this.getString(R.string.menuItem_player_three);
            break;
        case 4:
            title = this.getString(R.string.menuItem_player_four);
            break;
        }

        return title;
    }

    private MenuItem GetPlayerMenuItemFromId(int playerId) {
        MenuItem playerMenuItem = null;

        switch (playerId) {
        case 1:
            playerMenuItem = mGameSidebar.getMenu().findItem(R.id.menuItem_player_one);
            break;
        case 2:
            playerMenuItem = mGameSidebar.getMenu().findItem(R.id.menuItem_player_two);
            break;
        case 3:
            playerMenuItem = mGameSidebar.getMenu().findItem(R.id.menuItem_player_three);
            break;
        case 4:
            playerMenuItem = mGameSidebar.getMenu().findItem(R.id.menuItem_player_four);
            break;
        }

        return playerMenuItem;
    }

    public void setPakTypeFromPrompt(final int player) {
        //First get the prompt title
        final CharSequence title = GetPlayerTextFromId(player);
        final MenuItem playerMenuItem = GetPlayerMenuItemFromId(player);

        //Generate possible pak types
        final ArrayList<CharSequence> selections = new ArrayList<>();
        for (final PakType pakType : PakType.values()) {
            selections.add(this.getString(pakType.getResourceString()));
        }

        Prompt.promptListSelection(getActivity(), title, selections, new PromptIntegerListener() {
            @Override
            public void onDialogClosed(Integer value, int which) {
                if (which == DialogInterface.BUTTON_POSITIVE) {
                    mGlobalPrefs.putPakType(player, PakType.values()[value]);

                    // Set the pak in the core
                    mCoreFragment.updateControllerConfig(player - 1, true,
                            PakType.values()[value].getNativeValue());

                    //Update the menu
                    playerMenuItem.setTitleCondensed(
                            GameFragment.this.getString(mGlobalPrefs.getPakType(player).getResourceString()));
                    mGameSidebar.reload();
                }
            }
        });
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        Log.i("GameFragment", "surfaceCreated");
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        Log.i("GameFragment", "surfaceChanged");
        mIsSurface = true;
        mCoreFragment.setSurface(holder.getSurface());
        tryRunning();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        Log.i("GameFragment", "surfaceDestroyed");

        mCoreFragment.destroySurface();

        mIsSurface = false;
    }

    @Override
    public void onRestart(boolean shouldRestart) {
        if (shouldRestart) {
            mCoreFragment.restartEmulator();

            if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
                mDrawerLayout.closeDrawer(GravityCompat.START);
            }
        } else if (!mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
            mCoreFragment.resumeEmulator();
        }

        mWaitingOnConfirmation = false;
    }

    @Override
    public void onCoreServiceStarted() {
        ReloadAllMenus();
    }

    @Override
    public void onExitRequested(boolean shouldExit) {
        Log.i("GameFragment", "onExitRequested");
        if (shouldExit) {
            mMogaController.exit();
            shutdownEmulator();
        } else if (!mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
            mCoreFragment.resumeEmulator();
        }

        mWaitingOnConfirmation = false;
    }

    @Override
    public void onExitFinished() {
        if (getActivity() != null) {
            if (getActivity() instanceof OnGameActivityFinished) {
                ((OnGameActivityFinished) getActivity()).onGameActivityFinished();
            }
        }
    }

    /**
     * Handle view onKey callbacks
     * @param view If view is NULL then this keycode will not be handled by the key provider. This is to avoid
     *             the situation where user maps the menu key to the menu command.
     * @param keyCode key code
     * @param event key event
     * @return True if handled
     */
    @Override
    public boolean onKey(View view, int keyCode, KeyEvent event) {
        final boolean keyDown = event.getAction() == KeyEvent.ACTION_DOWN;

        boolean handled = false;

        // Attempt to reconnect any disconnected devices
        mGamePrefs.playerMap.reconnectDevice(AbstractProvider.getHardwareId(event));

        if (!mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
            // If PeripheralControllers exist and handle the event,
            // they return true. Else they return false, signaling
            // Android to handle the event (menu button, vol keys).
            if (mKeyProvider != null && view != null) {
                handled = mKeyProvider.onKey(view, keyCode, event);

                //Don't use built in keys in the device to hide the touch controls
                if (handled && keyCode != KeyEvent.KEYCODE_MENU && keyCode != KeyEvent.KEYCODE_BACK
                        && keyCode != KeyEvent.KEYCODE_VOLUME_UP && keyCode != KeyEvent.KEYCODE_VOLUME_DOWN
                        && keyCode != KeyEvent.KEYCODE_VOLUME_MUTE && mGlobalPrefs.touchscreenAutoHideEnabled) {
                    mOverlay.onTouchControlsHide();
                }
            }
        }

        if (!handled) {
            if (keyDown && keyCode == KeyEvent.KEYCODE_MENU) {
                if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
                    mDrawerLayout.closeDrawer(GravityCompat.START);
                    mOverlay.requestFocus();
                } else {
                    mCoreFragment.pauseEmulator();
                    mDrawerLayout.openDrawer(GravityCompat.START);
                    mDrawerOpenState = true;
                    mGameSidebar.requestFocus();
                    mGameSidebar.smoothScrollToPosition(0);
                }
                return true;
            } else if (keyDown && keyCode == KeyEvent.KEYCODE_BACK) {
                if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
                    mDrawerLayout.closeDrawer(GravityCompat.START);
                    mOverlay.requestFocus();
                } else {
                    //We are using the slide gesture for the menu, so the back key can be used to exit
                    if (mGlobalPrefs.inGameMenuIsSwipGesture) {
                        mWaitingOnConfirmation = true;
                        mCoreFragment.exit();
                    }
                    //Else the back key bring up the in-game menu
                    else {
                        mCoreFragment.pauseEmulator();
                        mDrawerLayout.openDrawer(GravityCompat.START);
                        mDrawerOpenState = true;
                        mGameSidebar.requestFocus();
                        mGameSidebar.smoothScrollToPosition(0);
                    }
                }
                return true;
            }
        }

        return handled;
    }

    @SuppressLint("InlinedApi")
    private void initControllers(View inputSource) {
        // By default, send Player 1 rumbles through phone vibrator
        final Vibrator vibrator = (Vibrator) getActivity().getSystemService(Context.VIBRATOR_SERVICE);
        mCoreFragment.registerVibrator(1, vibrator);

        // Create the touchscreen controls
        if (mGamePrefs.isTouchscreenEnabled) {
            if (!mGamePrefs.sensorAxisX.isEmpty() || !mGamePrefs.sensorAxisY.isEmpty()) {
                // Create the sensor controller
                final SensorManager sensorManager = (SensorManager) getActivity()
                        .getSystemService(Context.SENSOR_SERVICE);
                mSensorController = new SensorController(mCoreFragment, sensorManager, mOverlay,
                        mGamePrefs.sensorAxisX, mGamePrefs.sensorSensitivityX, mGamePrefs.sensorAngleX,
                        mGamePrefs.sensorAxisY, mGamePrefs.sensorSensitivityY, mGamePrefs.sensorAngleY);
                if (mGamePrefs.sensorActivateOnStart) {
                    mSensorController.setSensorEnabled(true);
                    mOverlay.onSensorEnabled(true);
                }
            }

            // Create the touchscreen controller
            mTouchscreenController = new TouchController(mCoreFragment, mTouchscreenMap, mOverlay, vibrator,
                    mGamePrefs.touchscreenAutoHold, mGlobalPrefs.isTouchscreenFeedbackEnabled,
                    mGamePrefs.touchscreenNotAutoHoldables, mSensorController, mGamePrefs.invertTouchXAxis,
                    mGamePrefs.invertTouchYAxis);
            inputSource.setOnTouchListener(this);
            mDrawerLayout.setTouchMap(mTouchscreenMap);
        }

        //Check for controller configuration
        boolean needs1 = false;
        boolean needs2 = false;
        boolean needs3 = false;
        boolean needs4 = false;

        // Popup the multi-player dialog if necessary and abort if any players are unassigned
        final RomDatabase romDatabase = RomDatabase.getInstance();

        if (!romDatabase.hasDatabaseFile()) {
            romDatabase.setDatabaseFile(mAppData.mupen64plus_ini);
        }

        final RomDetail romDetail = romDatabase.lookupByMd5WithFallback(mRomMd5, new File(mRomPath), mRomCrc);
        if (romDetail.players > 1 && mGamePrefs.playerMap.isEnabled() && mGlobalPrefs.getPlayerMapReminder()) {
            mGamePrefs.playerMap.removeUnavailableMappings();
            needs1 = mGamePrefs.isControllerEnabled1 && !mGamePrefs.playerMap.isMapped(1);
            needs2 = mGamePrefs.isControllerEnabled2 && !mGamePrefs.playerMap.isMapped(2);
            needs3 = mGamePrefs.isControllerEnabled3 && !mGamePrefs.playerMap.isMapped(3) && romDetail.players > 2;
            needs4 = mGamePrefs.isControllerEnabled4 && !mGamePrefs.playerMap.isMapped(4) && romDetail.players > 3;

            if (needs1 || needs2 || needs3 || needs4) {
                Popups.showNeedsPlayerMap(getActivity());
            }
        }

        // Create the input providers shared among all peripheral controllers
        mKeyProvider = new KeyProvider(inputSource, ImeFormula.DEFAULT, mGlobalPrefs.unmappableKeyCodes);
        final MogaProvider mogaProvider = new MogaProvider(mMogaController);
        mAxisProvider = new AxisProvider();

        // Request focus for proper listening
        inputSource.requestFocus();
        // Create the peripheral controls to handle key/stick presses
        if (mGamePrefs.isControllerEnabled1 && !needs1) {
            final ControllerProfile p = mGamePrefs.controllerProfile1;
            new PeripheralController(mCoreFragment, 1, mGamePrefs.playerMap, p.getMap(), p.getDeadzone(),
                    p.getSensitivityX(), p.getSensitivityY(), mOverlay, this, mSensorController, mKeyProvider,
                    mAxisProvider, mogaProvider);
            Log.i("GameFragment", "Player 1 has been enabled");
        }
        if (mGamePrefs.isControllerEnabled2 && !needs2) {
            final ControllerProfile p = mGamePrefs.controllerProfile2;
            new PeripheralController(mCoreFragment, 2, mGamePrefs.playerMap, p.getMap(), p.getDeadzone(),
                    p.getSensitivityX(), p.getSensitivityY(), mOverlay, this, null, mKeyProvider, mAxisProvider,
                    mogaProvider);
            Log.i("GameFragment", "Player 2 has been enabled");
        }
        if (mGamePrefs.isControllerEnabled3 && !needs3) {
            final ControllerProfile p = mGamePrefs.controllerProfile3;
            new PeripheralController(mCoreFragment, 3, mGamePrefs.playerMap, p.getMap(), p.getDeadzone(),
                    p.getSensitivityX(), p.getSensitivityY(), mOverlay, this, null, mKeyProvider, mAxisProvider,
                    mogaProvider);
            Log.i("GameFragment", "Player 3 has been enabled");
        }
        if (mGamePrefs.isControllerEnabled4 && !needs4) {
            final ControllerProfile p = mGamePrefs.controllerProfile4;
            new PeripheralController(mCoreFragment, 4, mGamePrefs.playerMap, p.getMap(), p.getDeadzone(),
                    p.getSensitivityX(), p.getSensitivityY(), mOverlay, this, null, mKeyProvider, mAxisProvider,
                    mogaProvider);
            Log.i("GameFragment", "Player 4 has been enabled");
        }
    }

    private void hideSystemBars() {
        if (getActivity() != null) {
            if (mGlobalPrefs.isImmersiveModeEnabled) {
                getActivity().getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
                        | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
            }
        }
    }

    private void showSystemBars() {
        if (getActivity() != null) {

            final Window window = getActivity().getWindow();

            window.clearFlags(LayoutParams.FLAG_FULLSCREEN | LayoutParams.FLAG_LAYOUT_IN_SCREEN
                    | LayoutParams.FLAG_KEEP_SCREEN_ON);

            window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
        }
    }

    private boolean isSafeToRender() {
        return mIsResumed && mIsSurface && !mShuttingDown;
    }

    private synchronized void tryRunning() {
        if (isSafeToRender()) {
            if (!mCoreFragment.IsInProgress()) {
                final String latestSave = mGameDataManager.getLatestAutoSave();
                mCoreFragment.startCore(mAppData, mGlobalPrefs, mGamePrefs, mRomGoodName, mRomPath, mRomMd5,
                        mRomCrc, mRomHeaderName, mRomCountryCode, mRomArtPath, mRomLegacySave,
                        mGamePrefs.getCheatArgs(), mDoRestart, latestSave);
            } else if (mCoreFragment.hasServiceStarted()
                    && mCoreFragment.getState() == NativeConstants.EMULATOR_STATE_PAUSED) {
                if (!mDrawerLayout.isDrawerOpen(GravityCompat.START) && !mWaitingOnConfirmation
                        && !mDrawerOpenState) {
                    mCoreFragment.resumeEmulator();
                } else {
                    mCoreFragment.resumeEmulator();

                    //Sleep for a bit to allow screen to refresh while running, then pause
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    mCoreFragment.pauseEmulator();
                }
            }
        }
    }

    private void shutdownEmulator() {
        mShuttingDown = true;

        if (mCoreFragment.hasServiceStarted()) {
            //Generate auto save file
            if (mGlobalPrefs.maxAutoSaves != 0) {
                final String saveFileName = mGameDataManager.getAutoSaveFileName();
                mCoreFragment.autoSaveState(saveFileName, new CoreService.AutoSaveCompleteAction() {
                    @Override
                    public void onSaveStateComplete() {
                        mCoreFragment.shutdownEmulator();
                    }
                });
            } else {
                mCoreFragment.shutdownEmulator();
            }

            mGameDataManager.clearOldest();
        }
    }

    private void tryPausing() {
        mCoreFragment.pauseEmulator();
    }

    Runnable mLastTouchChecker = new Runnable() {
        @Override
        public void run() {
            Calendar calendar = Calendar.getInstance();
            int seconds = calendar.get(Calendar.SECOND);

            if (seconds - mLastTouchTime > mGlobalPrefs.touchscreenAutoHideSeconds) {
                mOverlay.onTouchControlsHide();
            }

            mHandler.postDelayed(mLastTouchChecker, 500);
        }
    };

    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {

        Calendar calendar = Calendar.getInstance();
        mLastTouchTime = calendar.get(Calendar.SECOND);

        return mTouchscreenController.onTouch(view, motionEvent);
    }

    @Override
    public boolean onGenericMotion(View v, MotionEvent motionEvent) {
        if (mGlobalPrefs.touchscreenAutoHideEnabled)
            mOverlay.onTouchControlsHide();

        // Attempt to reconnect any disconnected devices
        mGamePrefs.playerMap.reconnectDevice(AbstractProvider.getHardwareId(motionEvent));

        return (mAxisProvider.onGenericMotion(null, motionEvent)
                && !mDrawerLayout.isDrawerOpen(GravityCompat.START))
                || getActivity().onGenericMotionEvent(motionEvent);
    }
}