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

Java tutorial

Introduction

Here is the source code for paulscode.android.mupen64plusae.game.GameActivity.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 java.io.File;
import java.util.ArrayList;

import org.mupen64plusae.v3.alpha.R;

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.game.GameSurface.GameSurfaceCreatedListener;
import paulscode.android.mupen64plusae.hack.MogaHack;
import paulscode.android.mupen64plusae.input.AbstractController;
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.CoreInterface;
import paulscode.android.mupen64plusae.jni.CoreInterface.OnExitListener;
import paulscode.android.mupen64plusae.jni.CoreInterface.OnPromptFinishedListener;
import paulscode.android.mupen64plusae.jni.CoreInterface.OnRestartListener;
import paulscode.android.mupen64plusae.jni.CoreInterface.OnSaveLoadListener;
import paulscode.android.mupen64plusae.jni.NativeConstants;
import paulscode.android.mupen64plusae.jni.NativeExports;
import paulscode.android.mupen64plusae.jni.NativeInput;
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.RomDatabase;
import paulscode.android.mupen64plusae.util.RomDatabase.RomDetail;
import paulscode.android.mupen64plusae.util.RomHeader;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.graphics.drawable.BitmapDrawable;
import android.hardware.SensorManager;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.Vibrator;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.SurfaceHolder;
import android.view.View;
import android.view.Window;
import android.view.WindowManager.LayoutParams;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;

import com.bda.controller.Controller;

//@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 GameActivity extends AppCompatActivity implements PromptConfirmListener, SurfaceHolder.Callback,
        GameSidebarActionHandler, OnPromptFinishedListener, OnSaveLoadListener, GameSurfaceCreatedListener,
        OnExitListener, OnRestartListener {
    // Activity and views
    private GameSurface mSurface;
    private GameOverlay mOverlay;
    private GameDrawerLayout mDrawerLayout;
    private GameSidebar mGameSidebar;

    // Input resources
    private ArrayList<AbstractController> mControllers;
    private VisibleTouchMap mTouchscreenMap;
    private KeyProvider mKeyProvider;
    private Controller mMogaController;
    private SensorController mSensorController;

    // Intent data
    private String mRomPath;
    private String mRomMd5;
    private String mRomCrc;

    // 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 GlobalPrefs mGlobalPrefs;
    private GamePrefs mGamePrefs;
    private GameAutoSaveManager mAutoSaveManager;
    private boolean mFirstStart;
    private boolean mWaitingOnConfirmation = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.i("GameActivity", "onCreate");
        super.setTheme(android.support.v7.appcompat.R.style.Theme_AppCompat_NoActionBar);

        //Allow volume keys to control media volume if they are not mapped
        final SharedPreferences mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
        final boolean volKeyMapped = mPreferences.getBoolean("inputVolumeMappable", false);
        final AppData appData = new AppData(this);
        final GlobalPrefs globalPrefs = new GlobalPrefs(this, appData);
        if (!volKeyMapped && globalPrefs.audioPlugin.enabled) {
            setVolumeControlStream(AudioManager.STREAM_MUSIC);
        }

        mControllers = new ArrayList<>();
        mMogaController = Controller.getInstance(this);

        // Get the intent data
        final Bundle extras = this.getIntent().getExtras();
        if (extras == null)
            throw new Error("ROM path and MD5 must be passed via the extras bundle when starting GameActivity");
        mRomPath = extras.getString(ActivityHelper.Keys.ROM_PATH);
        mRomMd5 = extras.getString(ActivityHelper.Keys.ROM_MD5);
        mRomCrc = extras.getString(ActivityHelper.Keys.ROM_CRC);
        String romHeaderName = extras.getString(ActivityHelper.Keys.ROM_HEADER_NAME);
        byte romCountryCode = extras.getByte(ActivityHelper.Keys.ROM_COUNTRY_CODE);
        String artPath = extras.getString(ActivityHelper.Keys.ROM_ART_PATH);
        String romGoodName = extras.getString(ActivityHelper.Keys.ROM_GOOD_NAME);
        String legacySaveName = extras.getString(ActivityHelper.Keys.ROM_LEGACY_SAVE);
        boolean doRestart = extras.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 GameActivity");

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

        // Get app data and user preferences
        mGlobalPrefs = new GlobalPrefs(this, appData);

        mGamePrefs = new GamePrefs(this, mRomMd5, mRomCrc, romHeaderName, romGoodName,
                RomHeader.countryCodeToSymbol(romCountryCode), appData, mGlobalPrefs, legacySaveName);
        String cheatArgs = mGamePrefs.getCheatArgs();

        mAutoSaveManager = new GameAutoSaveManager(mGamePrefs, mGlobalPrefs.maxAutoSaves);

        mGlobalPrefs.enforceLocale(this);

        final Window window = this.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);

        // Set the screen orientation
        this.setRequestedOrientation(mGlobalPrefs.displayOrientation);

        // If the orientation changes, the screensize info changes, so we must refresh dependencies
        mGlobalPrefs = new GlobalPrefs(this, appData);
        mGamePrefs = new GamePrefs(this, mRomMd5, mRomCrc, romHeaderName, romGoodName,
                RomHeader.countryCodeToSymbol(romCountryCode), appData, mGlobalPrefs, legacySaveName);

        mFirstStart = true;

        //TODO: Figure out why we call this in the middle
        super.onCreate(savedInstanceState);

        // Lay out content and get the views
        this.setContentView(R.layout.game_activity);
        mSurface = (GameSurface) this.findViewById(R.id.gameSurface);
        mOverlay = (GameOverlay) this.findViewById(R.id.gameOverlay);
        mDrawerLayout = (GameDrawerLayout) this.findViewById(R.id.drawerLayout);
        mGameSidebar = (GameSidebar) this.findViewById(R.id.gameSidebar);

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

        // Make the background solid black
        mSurface.getRootView().setBackgroundColor(0xFF000000);
        mSurface.SetGameSurfaceCreatedListener(this);

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

        mGameSidebar.setTitle(romGoodName);
        // Initialize the objects and data files interfacing to the emulator core
        CoreInterface.initialize(this, mSurface, mGamePrefs, mRomPath, mRomMd5, cheatArgs, doRestart);

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

        //Reload menus
        ReloadAllMenus();

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

        // Update the GameSurface size
        mSurface.getHolder().setFixedSize(mGamePrefs.videoRenderWidth, mGamePrefs.videoRenderHeight);
        final FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mSurface.getLayoutParams();
        params.width = Math.round(mGamePrefs.videoSurfaceWidth * (mGamePrefs.videoSurfaceZoom / 100.f));
        params.height = Math.round(mGamePrefs.videoSurfaceHeight * (mGamePrefs.videoSurfaceZoom / 100.f));

        if ((mGlobalPrefs.displayOrientation & 1) == 1)
            params.gravity = mGlobalPrefs.displayPosition | Gravity.CENTER_HORIZONTAL;
        else
            params.gravity = Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL;

        mSurface.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,
                    mGamePrefs.isTouchscreenAnimated, mGlobalPrefs.isFpsEnabled, mGlobalPrefs.touchscreenScale,
                    mGlobalPrefs.touchscreenTransparency);
            mOverlay.initialize(mTouchscreenMap, !mGamePrefs.isTouchscreenHidden, mGlobalPrefs.isFpsEnabled,
                    mGamePrefs.isAnalogHiddenWhenSensor, mGamePrefs.isTouchscreenAnimated);
        }

        // Initialize user interface devices
        initControllers(mOverlay);

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

        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);
        }

        mDrawerLayout.setDrawerListener(new DrawerLayout.DrawerListener() {

            @Override
            public void onDrawerClosed(View arg0) {
                NativeExports.emuResume();
            }

            @Override
            public void onDrawerOpened(View arg0) {
                NativeExports.emuPause();
                ReloadAllMenus();
            }

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

            }

            @Override
            public void onDrawerStateChanged(int newState) {

            }

        });
    }

    @SuppressWarnings("deprecation")
    @Override
    protected void onResume() {
        super.onResume();

        Log.i("GameActivity", "onResume");
        mIsResumed = true;

        tryRunning();

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

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

        mMogaController.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();

        Log.i("GameActivity", "onPause");
        mIsResumed = false;
        tryPausing();

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

        mMogaController.onPause();
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);

        // Only try to run; don't try to pause. User may just be touching the in-game menu.
        Log.i("GameActivity", "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) {
        CoreInterface.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, NativeExports.emuGetSpeed()));

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

        final int resId = NativeExports.emuGetFramelimiter() ? 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, NativeExports.emuGetSlot()));

        //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, NativeExports.emuGetSpeed()));

        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;
            CoreInterface.exit();
            break;
        case R.id.menuItem_toggle_speed:
            CoreInterface.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, NativeExports.emuGetSpeed()));
            mGameSidebar.reload();
            break;
        case R.id.menuItem_set_speed:
            CoreInterface.setCustomSpeedFromPrompt(this);
            break;
        case R.id.menuItem_screenshot:
            CoreInterface.screenshot();
            break;
        case R.id.menuItem_set_slot:
            CoreInterface.setSlotFromPrompt(this);
            break;
        case R.id.menuItem_slot_load:
            CoreInterface.loadSlot(this);
            break;
        case R.id.menuItem_slot_save:
            CoreInterface.saveSlot(this);
            if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
                mDrawerLayout.closeDrawer(GravityCompat.START);
            }
            break;
        case R.id.menuItem_file_load:
            CoreInterface.loadFileFromPrompt(this);
            break;
        case R.id.menuItem_file_save:
            CoreInterface.saveFileFromPrompt();
            break;
        case R.id.menuItem_file_load_auto_save:
            CoreInterface.loadAutoSaveFromPrompt(this);
            break;
        case R.id.menuItem_disable_frame_limiter:
            CoreInterface.toggleFramelimiter();

            final int resId = NativeExports.emuGetFramelimiter() ? 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) this
                    .getSystemService(Context.INPUT_METHOD_SERVICE);
            if (imeManager != null)
                imeManager.showInputMethodPicker();
            break;
        case R.id.menuItem_reset:
            mWaitingOnConfirmation = true;
            CoreInterface.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(this, 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
                    NativeInput.setConfig(player - 1, true, PakType.values()[value].getNativeValue());

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

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

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        Log.i("GameActivity", "surfaceChanged");
        NativeExports.notifySDLSurfaceReady();
        mIsSurface = true;
        tryRunning();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        Log.i("GameActivity", "surfaceDestroyed");
        NativeExports.notifySDLSurfaceDestroyed();
        mSurface.setEGLContextNotReady();
        mIsSurface = false;
        tryPausing();
    }

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

            if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
                mDrawerLayout.closeDrawer(GravityCompat.START);
            }
        } else if (!mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
            NativeExports.emuResume();
        }

        mWaitingOnConfirmation = false;
    }

    @Override
    public void onExit(boolean shouldExit) {
        if (shouldExit) {
            // Never go directly from running to stopped; always pause (and autosave) first
            final String saveFileName = mAutoSaveManager.getAutoSaveFileName();
            CoreInterface.pauseEmulator(true, saveFileName);
            mAutoSaveManager.clearOldest();
            CoreInterface.shutdownEmulator();

            mMogaController.exit();

            this.finish();
        } else if (!mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
            NativeExports.emuResume();
        }

        mWaitingOnConfirmation = false;
    }

    @Override
    public boolean onKey(View view, int keyCode, KeyEvent event) {
        final boolean keyDown = event.getAction() == KeyEvent.ACTION_DOWN;

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

        if (keyDown && keyCode == KeyEvent.KEYCODE_MENU) {
            if (mDrawerLayout.isDrawerOpen(GravityCompat.START))
                mDrawerLayout.closeDrawer(GravityCompat.START);
            else
                mDrawerLayout.openDrawer(GravityCompat.START);
            return true;
        } else if (keyDown && keyCode == KeyEvent.KEYCODE_BACK) {
            if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
                mDrawerLayout.closeDrawer(GravityCompat.START);
            } else {
                mWaitingOnConfirmation = true;
                CoreInterface.exit();
            }
            return true;
        }

        // Let the PeripheralControllers and Android handle everything else
        else {
            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)
                    return mKeyProvider.onKey(view, keyCode, event);
            }

            return false;
        }
    }

    @SuppressLint("InlinedApi")
    private void initControllers(View inputSource) {
        // By default, send Player 1 rumbles through phone vibrator
        final Vibrator vibrator = (Vibrator) this.getSystemService(Context.VIBRATOR_SERVICE);
        CoreInterface.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) getSystemService(Context.SENSOR_SERVICE);
                mSensorController = new SensorController(sensorManager, mOverlay, mGamePrefs.sensorAxisX,
                        mGamePrefs.sensorSensitivityX, mGamePrefs.sensorAngleX, mGamePrefs.sensorAxisY,
                        mGamePrefs.sensorSensitivityY, mGamePrefs.sensorAngleY);
                mControllers.add(mSensorController);
                if (mGamePrefs.sensorActivateOnStart) {
                    mSensorController.setSensorEnabled(true);
                    mOverlay.onSensorEnabled(true);
                }
            }

            // Create the touchscreen controller
            final TouchController touchscreenController = new TouchController(mTouchscreenMap, inputSource,
                    mOverlay, vibrator, mGlobalPrefs.touchscreenAutoHold, mGlobalPrefs.isTouchscreenFeedbackEnabled,
                    mGamePrefs.touchscreenAutoHoldables, mSensorController);
            mControllers.add(touchscreenController);

            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()) {
            final AppData appData = new AppData(this);
            romDatabase.setDatabaseFile(appData.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) {
                // TODO FIXME
                //                  @SuppressWarnings( "deprecation" )
                //                  PlayerMapPreference pref = (PlayerMapPreference) findPreference( "playerMap" );
                //                  pref.show();
                //                  return;
                Popups.showNeedsPlayerMap(this);
            }
        }

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

        // Create the peripheral controls to handle key/stick presses
        if (mGamePrefs.isControllerEnabled1 && !needs1) {
            final ControllerProfile p = mGamePrefs.controllerProfile1;
            mControllers.add(new PeripheralController(1, mGamePrefs.playerMap, p.getMap(), p.getDeadzone(),
                    p.getSensitivity(), mOverlay, mSensorController, mKeyProvider, axisProvider, mogaProvider));
        }
        if (mGamePrefs.isControllerEnabled2 && !needs2) {
            final ControllerProfile p = mGamePrefs.controllerProfile2;
            mControllers.add(new PeripheralController(2, mGamePrefs.playerMap, p.getMap(), p.getDeadzone(),
                    p.getSensitivity(), mOverlay, null, mKeyProvider, axisProvider, mogaProvider));
        }
        if (mGamePrefs.isControllerEnabled3 && !needs3) {
            final ControllerProfile p = mGamePrefs.controllerProfile3;
            mControllers.add(new PeripheralController(3, mGamePrefs.playerMap, p.getMap(), p.getDeadzone(),
                    p.getSensitivity(), mOverlay, null, mKeyProvider, axisProvider, mogaProvider));
        }
        if (mGamePrefs.isControllerEnabled4 && !needs4) {
            final ControllerProfile p = mGamePrefs.controllerProfile4;
            mControllers.add(new PeripheralController(4, mGamePrefs.playerMap, p.getMap(), p.getDeadzone(),
                    p.getSensitivity(), mOverlay, null, mKeyProvider, axisProvider, mogaProvider));
        }
    }

    @SuppressLint("InlinedApi")
    private void hideSystemBars() {
        if (mDrawerLayout != null) {
            if (AppData.IS_KITKAT && mGlobalPrefs.isImmersiveModeEnabled) {
                mDrawerLayout.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);
            } else {
                mDrawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); // == STATUS_BAR_HIDDEN for Honeycomb
            }
        }
    }

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

    private void tryRunning() {
        final int state = NativeExports.emuGetState();
        if (isSafeToRender() && (state != NativeConstants.EMULATOR_STATE_RUNNING)) {
            switch (state) {
            case NativeConstants.EMULATOR_STATE_UNKNOWN:
                final String latestSave = mAutoSaveManager.getLatestAutoSave();
                CoreInterface.startupEmulator(latestSave);
                break;
            case NativeConstants.EMULATOR_STATE_PAUSED:
                if (mSurface.isEGLContextReady() && !mDrawerLayout.isDrawerOpen(GravityCompat.START)
                        && !mWaitingOnConfirmation)
                    CoreInterface.resumeEmulator();
                break;
            default:
                break;
            }
        }
    }

    private void tryPausing() {
        if (NativeExports.emuGetState() != NativeConstants.EMULATOR_STATE_PAUSED)
            CoreInterface.pauseEmulator(false, null);
    }

    @Override
    public void onGameSurfaceCreated() {
        if (!mDrawerLayout.isDrawerOpen(GravityCompat.START) && !mWaitingOnConfirmation) {
            NativeExports.emuResume();
        } else {
            //Advance 1 frame so that something is shown instead of a black screen
            CoreInterface.advanceFrame();
        }
    }
}