paulscode.android.mupen64plusae.SplashActivity.java Source code

Java tutorial

Introduction

Here is the source code for paulscode.android.mupen64plusae.SplashActivity.java

Source

/**
 * Mupen64PlusAE, an N64 emulator for the Android platform
 *
 * Copyright (C) 2012 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 2 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: paulscode, lioncash, littleguy77
 */

package paulscode.android.mupen64plusae;

import java.io.File;
import java.util.List;

import org.mupen64plusae.v3.alpha.R;

import paulscode.android.mupen64plusae.cheat.CheatUtils;
import paulscode.android.mupen64plusae.dialog.Popups;
import paulscode.android.mupen64plusae.persistent.AppData;
import paulscode.android.mupen64plusae.persistent.GlobalPrefs;
import paulscode.android.mupen64plusae.preference.PathPreference;
import paulscode.android.mupen64plusae.preference.PrefUtil;
import paulscode.android.mupen64plusae.task.ExtractAssetsTask;
import paulscode.android.mupen64plusae.task.ExtractAssetsTask.ExtractAssetsListener;
import paulscode.android.mupen64plusae.task.ExtractAssetsTask.Failure;
import paulscode.android.mupen64plusae.util.FileUtil;
import paulscode.android.mupen64plusae.util.Notifier;
import paulscode.android.mupen64plusae.util.RomDatabase;
import tv.ouya.console.api.OuyaFacade;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.preference.PreferenceManager;
import android.text.Html;
import android.widget.LinearLayout;
import android.view.WindowManager.LayoutParams;
import android.widget.ImageView;
import android.widget.TextView;

/**
 * The main activity that presents the splash screen, extracts the assets if necessary, and launches
 * the main menu activity.
 */
public class SplashActivity extends AppCompatActivity
        implements ExtractAssetsListener, OnRequestPermissionsResultCallback {
    //Permission request ID
    static final int PERMISSION_REQUEST = 177;

    //Total number of permissions requested
    static final int NUM_PERMISSIONS = 2;

    /**
     * Asset version number, used to determine stale assets. Increment this number every time the
     * assets are updated on disk.
     */
    private static final int ASSET_VERSION = 79;

    /** The total number of assets to be extracted (for computing progress %). */
    private static final int TOTAL_ASSETS = 120;

    /** The minimum duration that the splash screen is shown, in milliseconds. */
    private static final int SPLASH_DELAY = 1000;

    /** PaulsCode OUYA developer UUID */
    private static final String DEVELOPER_ID = "68d84579-c1e2-4418-8976-cda2692133f1";

    /**
     * The subdirectory within the assets directory to extract. A subdirectory is necessary to avoid
     * extracting all the default system assets in addition to ours.
     */
    private static final String SOURCE_DIR = "mupen64plus_data";

    /** The text view that displays extraction progress info. */
    private TextView mTextView;

    /** The running count of assets extracted. */
    private int mAssetsExtracted;

    // App data and user preferences
    private AppData mAppData = null;
    private GlobalPrefs mGlobalPrefs = null;
    private SharedPreferences mPrefs = null;

    // These constants must match the keys used in res/xml/preferences*.xml
    private static final String DISPLAY_ORIENTATION = "displayOrientation";
    private static final String DISPLAY_POSITION = "displayPosition";
    private static final String DISPLAY_SCALING = "displayScaling";
    private static final String VIDEO_HARDWARE_TYPE = "videoHardwareType";
    private static final String AUDIO_PLUGIN = "audioPlugin";
    private static final String AUDIO_SDL_BUFFER_SIZE = "audioSDLBufferSize";
    private static final String AUDIO_SLES_BUFFER_SIZE = "audioSLESBufferSize2";
    private static final String TOUCHSCREEN_AUTO_HOLD = "touchscreenAutoHold";
    private static final String NAVIGATION_MODE = "navigationMode";
    private static final String DATA_PATH = "pathGameSaves";

    /*
     * (non-Javadoc)
     *
     * @see android.app.Activity#onCreate(android.os.Bundle)
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Get app data and user preferences
        mAppData = new AppData(this);
        mGlobalPrefs = new GlobalPrefs(this, mAppData);
        mGlobalPrefs.enforceLocale(this);
        mPrefs = PreferenceManager.getDefaultSharedPreferences(this);

        // Ensure that any missing preferences are populated with defaults (e.g. preference added to
        // new release)
        PreferenceManager.setDefaultValues(this, R.xml.preferences_audio, false);
        PreferenceManager.setDefaultValues(this, R.xml.preferences_data, false);
        PreferenceManager.setDefaultValues(this, R.xml.preferences_display, false);
        PreferenceManager.setDefaultValues(this, R.xml.preferences_input, false);
        PreferenceManager.setDefaultValues(this, R.xml.preferences_library, false);
        PreferenceManager.setDefaultValues(this, R.xml.preferences_touchscreen, false);

        // Ensure that selected plugin names and other list preferences are valid
        // @formatter:off
        final Resources res = getResources();
        PrefUtil.validateListPreference(res, mPrefs, DISPLAY_ORIENTATION, R.string.displayOrientation_default,
                R.array.displayOrientation_values);
        PrefUtil.validateListPreference(res, mPrefs, DISPLAY_POSITION, R.string.displayPosition_default,
                R.array.displayPosition_values);
        PrefUtil.validateListPreference(res, mPrefs, DISPLAY_SCALING, R.string.displayScaling_default,
                R.array.displayScaling_values);
        PrefUtil.validateListPreference(res, mPrefs, VIDEO_HARDWARE_TYPE, R.string.videoHardwareType_default,
                R.array.videoHardwareType_values);
        PrefUtil.validateListPreference(res, mPrefs, AUDIO_PLUGIN, R.string.audioPlugin_default,
                R.array.audioPlugin_values);
        PrefUtil.validateListPreference(res, mPrefs, AUDIO_SDL_BUFFER_SIZE, R.string.audioSDLBufferSize_default,
                R.array.audioSDLBufferSize_values);
        PrefUtil.validateListPreference(res, mPrefs, AUDIO_SLES_BUFFER_SIZE, R.string.audioSLESBufferSize_default,
                R.array.audioSLESBufferSize_values);
        PrefUtil.validateListPreference(res, mPrefs, TOUCHSCREEN_AUTO_HOLD, R.string.touchscreenAutoHold_default,
                R.array.touchscreenAutoHold_values);
        PrefUtil.validateListPreference(res, mPrefs, NAVIGATION_MODE, R.string.navigationMode_default,
                R.array.navigationMode_values);

        if (!mPrefs.getBoolean("seen_survey", false)) {
            LinearLayout dialogLayout = new LinearLayout(this);
            dialogLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
                    LinearLayout.LayoutParams.WRAP_CONTENT));
            dialogLayout.setOrientation(LinearLayout.VERTICAL);
            TextView message = new TextView(this);
            message.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
                    LinearLayout.LayoutParams.WRAP_CONTENT));
            message.setText("We have recently changed our icon and we want your feedback!\n\n"
                    + "Please take a few moments to fill out this feedback form so we can make improvements. "
                    + "You will need a Google account to fill out this form. This is so we don't have duplicates.");
            message.setPadding(16, 16, 16, 16);
            dialogLayout.addView(message);
            ImageView icon = new ImageView(this);
            icon.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
                    LinearLayout.LayoutParams.WRAP_CONTENT));
            icon.setAdjustViewBounds(true);
            icon.setPadding(16, 16, 16, 16);
            icon.setImageResource(R.drawable.ic_playstore_ouya);
            dialogLayout.addView(icon);
            AlertDialog dialog = new AlertDialog.Builder().setTitle("We want your feedback!").setView(iconLayout)
                    .setPositiveButton(getString(android.R.string.ok), new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(
                                    "https://docs.google.com/forms/d/1F2stkHvb0Rx5vfmaz50OtTfT73XjHK2GEXZj9a_x-yw/viewform"));
                            SplashActivity.this.startActivity(i);
                        }
                    }).setNegativeButton(getString(android.R.string.cancel), null).create();
            mPrefs.edit().putBoolean("seen_survey", true).apply();
            dialog.show();
        }
        //Check for invalid data path
        String dataPathString = mPrefs.getString(DATA_PATH, null);

        if (dataPathString == null || dataPathString.isEmpty()
                || dataPathString.contains(res.getString(R.string.pathGameSaves_default))) {
            String defValue = res.getString(R.string.pathGameSaves_default);
            String newDefValue = PathPreference.validate(defValue);

            mPrefs.edit().putString(DATA_PATH, newDefValue).commit();
        }

        // @formatter:on

        // Refresh the preference data wrapper
        mGlobalPrefs = new GlobalPrefs(this, mAppData);

        // Make sure custom skin directory exist
        new File(mGlobalPrefs.touchscreenCustomSkinsDir).mkdirs();

        // Initialize the OUYA interface if running on OUYA
        if (AppData.IS_OUYA_HARDWARE)
            OuyaFacade.getInstance().init(this, DEVELOPER_ID);

        // Initialize the toast/status bar notifier
        Notifier.initialize(this);

        // Don't let the activity sleep in the middle of extraction
        getWindow().setFlags(LayoutParams.FLAG_KEEP_SCREEN_ON, LayoutParams.FLAG_KEEP_SCREEN_ON);

        // Lay out the content
        setContentView(R.layout.splash_activity);
        mTextView = (TextView) findViewById(R.id.mainText);

        if (mGlobalPrefs.isBigScreenMode) {
            final ImageView splash = (ImageView) findViewById(R.id.mainImage);
            splash.setImageResource(R.drawable.publisherlogo_ouya);
        }

        requestPermissions();

        // Popup a warning if the installation appears to be corrupt
        if (!mAppData.isValidInstallation()) {
            Popups.showInvalidInstall(this);
        }
    }

    public void requestPermissions() {
        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
                || ContextCompat.checkSelfPermission(this,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            // Should we show an explanation?
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)
                    || ActivityCompat.shouldShowRequestPermissionRationale(this,
                            Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                //Show dialog asking for permissions
                new AlertDialog.Builder(this).setTitle(getString(R.string.assetExtractor_permissions_title))
                        .setMessage(getString(R.string.assetExtractor_permissions_rationale))
                        .setPositiveButton(getString(android.R.string.ok), new OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                actuallyRequestPermissions();
                            }

                        }).setNegativeButton(getString(android.R.string.cancel), new OnClickListener() {
                            //Show dialog stating that the app can't continue without proper permissions
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                new AlertDialog.Builder(SplashActivity.this)
                                        .setTitle(getString(R.string.assetExtractor_error))
                                        .setMessage(getString(R.string.assetExtractor_failed_permissions))
                                        .setPositiveButton(getString(android.R.string.ok), new OnClickListener() {
                                            @Override
                                            public void onClick(DialogInterface dialog, int which) {
                                                SplashActivity.this.finish();
                                            }

                                        }).setCancelable(false).show();
                            }
                        }).setCancelable(false).show();
            } else {
                // No explanation needed, we can request the permission.
                actuallyRequestPermissions();
            }
        } else {
            // Extract the assets in a separate thread and launch the menu activity
            // Handler.postDelayed ensures this runs only after activity has resumed
            final Handler handler = new Handler();
            handler.postDelayed(extractAssetsTaskLauncher, SPLASH_DELAY);
        }
    }

    @SuppressLint("InlinedApi")
    public void actuallyRequestPermissions() {
        ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.READ_EXTERNAL_STORAGE,
                Manifest.permission.WRITE_EXTERNAL_STORAGE }, PERMISSION_REQUEST);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
        case PERMISSION_REQUEST: {
            // If request is cancelled, the result arrays are empty.
            boolean good = true;
            if (permissions.length != NUM_PERMISSIONS || grantResults.length != NUM_PERMISSIONS) {
                good = false;
            }

            for (int i = 0; i < grantResults.length && good; i++) {
                if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
                    good = false;
                }
            }

            if (!good) {
                // permission denied, boo! Disable the app.
                new AlertDialog.Builder(SplashActivity.this).setTitle(getString(R.string.assetExtractor_error))
                        .setMessage(getString(R.string.assetExtractor_failed_permissions))
                        .setPositiveButton(getString(android.R.string.ok), new OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                SplashActivity.this.finish();
                            }

                        }).setCancelable(false).show();
            } else {
                //Permissions already granted, continue
                // Extract the assets in a separate thread and launch the menu activity
                // Handler.postDelayed ensures this runs only after activity has resumed
                final Handler handler = new Handler();
                handler.postDelayed(extractAssetsTaskLauncher, SPLASH_DELAY);
            }
            return;
        }

        // other 'case' lines to check for other
        // permissions this app might request
        }
    }

    /** Runnable that launches the non-UI thread from the UI thread after the activity has resumed. */
    private final Runnable extractAssetsTaskLauncher = new Runnable() {
        @Override
        public void run() {
            if (mAppData.getAssetVersion() != ASSET_VERSION) {
                extractAssets();
            } else {
                // Assets already extracted, just launch gallery activity, passing ROM path if it was provided externally
                ActivityHelper.startGalleryActivity(SplashActivity.this, getIntent().getData());

                // We never want to come back to this activity, so finish it
                finish();
            }
        }
    };

    /**
     * Extract assets
     */
    private void extractAssets() {
        // Extract and merge the assets if they are out of date
        FileUtil.deleteFolder(new File(mAppData.coreSharedDataDir));
        mAssetsExtracted = 0;
        new ExtractAssetsTask(getAssets(), SOURCE_DIR, mAppData.coreSharedDataDir, SplashActivity.this).execute();
    }

    @Override
    public void onExtractAssetsProgress(String nextFileToExtract) {
        final float percent = (100f * mAssetsExtracted) / TOTAL_ASSETS;
        final String text = getString(R.string.assetExtractor_progress, percent, nextFileToExtract);
        mTextView.setText(text);
        mAssetsExtracted++;
    }

    @Override
    public void onExtractAssetsFinished(List<Failure> failures) {
        if (failures.size() == 0) {
            // Extraction succeeded, record new asset version and merge cheats
            mTextView.setText(R.string.assetExtractor_finished);
            mAppData.putAssetVersion(ASSET_VERSION);
            CheatUtils.mergeCheatFiles(mAppData.mupencheat_default, mGlobalPrefs.customCheats_txt,
                    mAppData.mupencheat_txt);

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

            // Launch gallery activity, passing ROM path if it was provided externally
            ActivityHelper.startGalleryActivity(this, getIntent().getData());

            // We never want to come back to this activity, so finish it
            finish();
        } else {
            // Extraction failed, update the on-screen text and don't start next activity
            final String weblink = getResources().getString(R.string.assetExtractor_uriHelp);
            final String message = getString(R.string.assetExtractor_failed, weblink);
            String textHtml = message.replace("\n", "<br/>") + "<p><small>";
            for (final Failure failure : failures) {
                textHtml += failure.toString() + "<br/>";
            }
            textHtml += "</small>";
            mTextView.setText(Html.fromHtml(textHtml));
        }
    }
}