Move emulation lifecycle handling into EmulationFragment.

The Activity is responsible for just its views and menus and such. It
signals the Fragment via setGamePath, StartEmulation and StopEmulation.

The Fragment manages the actual emulation lifecycle. It is solely
responsible for calling the NativeLibrary lifecycle methods.

With this lifecycle simplification, the NativeLibrary no longer needs to
kill the Activity. It happens normally now.

This simplifies a lot of things, live handling rotation.
This commit is contained in:
Mike Harris 2017-10-05 00:21:37 -07:00
parent d48c64457a
commit 4cab718065
6 changed files with 162 additions and 178 deletions

View File

@ -399,20 +399,6 @@ public final class NativeLibrary
}
}
public static void endEmulationActivity()
{
Log.verbose("[NativeLibrary] Ending EmulationActivity.");
EmulationActivity emulationActivity = sEmulationActivity.get();
if (emulationActivity != null)
{
emulationActivity.exitWithAnimation();
}
else
{
Log.warning("[NativeLibrary] EmulationActivity is null, can't end.");
}
}
public static void setEmulationActivity(EmulationActivity emulationActivity)
{
Log.verbose("[NativeLibrary] Registering EmulationActivity.");

View File

@ -23,7 +23,6 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.SeekBar;
@ -55,8 +54,8 @@ public final class EmulationActivity extends AppCompatActivity
private static final String FRAGMENT_SUBMENU_TAG = "submenu";
private View mDecorView;
private ImageView mImageView;
private EmulationFragment mEmulationFragment;
private FrameLayout mFrameEmulation;
private LinearLayout mMenuLayout;
private SharedPreferences mPreferences;
@ -85,7 +84,6 @@ public final class EmulationActivity extends AppCompatActivity
}
};
private String mScreenPath;
private FrameLayout mFrameContent;
private String mSelectedTitle;
@Retention(SOURCE)
@ -219,12 +217,13 @@ public final class EmulationActivity extends AppCompatActivity
Java_GCAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE);
Java_WiimoteAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE);
setContentView(R.layout.activity_emulation);
mImageView = (ImageView) findViewById(R.id.image_screenshot);
mFrameContent = (FrameLayout) findViewById(R.id.frame_content);
mFrameEmulation = (FrameLayout) findViewById(R.id.frame_emulation_fragment);
mMenuLayout = (LinearLayout) findViewById(R.id.layout_ingame_menu);
mEmulationFragment = (EmulationFragment) getSupportFragmentManager()
.findFragmentById(R.id.fragment_emulation);
Intent gameToEmulate = getIntent();
String path = gameToEmulate.getStringExtra("SelectedGame");
@ -260,14 +259,6 @@ public final class EmulationActivity extends AppCompatActivity
Animations.fadeViewOut(mImageView)
.setStartDelay(2000)
.withStartAction(new Runnable()
{
@Override
public void run()
{
mFrameEmulation.setVisibility(View.VISIBLE);
}
})
.withEndAction(new Runnable()
{
@Override
@ -277,18 +268,12 @@ public final class EmulationActivity extends AppCompatActivity
}
});
// Instantiate an EmulationFragment.
EmulationFragment emulationFragment = EmulationFragment.newInstance(path);
// Add fragment to the activity - this triggers all its lifecycle callbacks.
getSupportFragmentManager().beginTransaction()
.add(R.id.frame_emulation_fragment, emulationFragment, EmulationFragment.FRAGMENT_TAG)
.commit();
mEmulationFragment.setGamePath(path);
mEmulationFragment.startEmulation();
}
else
{
mImageView.setVisibility(View.GONE);
mFrameEmulation.setVisibility(View.VISIBLE);
}
if (mDeviceHasTouchScreen)
@ -311,23 +296,6 @@ public final class EmulationActivity extends AppCompatActivity
mIsGameCubeGame = Platform.fromNativeInt(NativeLibrary.GetPlatform(path)) == Platform.GAMECUBE;
}
@Override
protected void onStart()
{
super.onStart();
Log.debug("[EmulationActivity] EmulationActivity starting.");
NativeLibrary.setEmulationActivity(this);
}
@Override
protected void onStop()
{
super.onStop();
Log.debug("[EmulationActivity] EmulationActivity stopping.");
NativeLibrary.clearEmulationActivity();
}
@Override
protected void onPostCreate(Bundle savedInstanceState)
{
@ -376,7 +344,8 @@ public final class EmulationActivity extends AppCompatActivity
}
else
{
stopEmulation();
mEmulationFragment.stopEmulation();
exitWithAnimation();
}
}
@ -406,15 +375,6 @@ public final class EmulationActivity extends AppCompatActivity
}
}
private void stopEmulation()
{
EmulationFragment fragment = (EmulationFragment) getSupportFragmentManager()
.findFragmentByTag(EmulationFragment.FRAGMENT_TAG);
fragment.notifyEmulationStopped();
NativeLibrary.StopEmulation();
}
public void exitWithAnimation()
{
runOnUiThread(new Runnable()
@ -458,7 +418,6 @@ public final class EmulationActivity extends AppCompatActivity
@Override
public void run()
{
mFrameContent.removeView(mFrameEmulation);
setResult(mPosition);
finishAfterTransition();
}
@ -599,20 +558,23 @@ public final class EmulationActivity extends AppCompatActivity
return;
case MENU_ACTION_EXIT:
toggleMenu();
stopEmulation();
toggleMenu(); // Hide the menu (it will be showing since we just clicked it)
mEmulationFragment.stopEmulation();
exitWithAnimation();
return;
}
}
private void editControlsPlacement() {
EmulationFragment emulationFragment = (EmulationFragment) getSupportFragmentManager()
.findFragmentById(R.id.frame_emulation_fragment);
if (emulationFragment.isConfiguringControls()) {
emulationFragment.stopConfiguringControls();
} else {
emulationFragment.startConfiguringControls();
private void editControlsPlacement()
{
if (mEmulationFragment.isConfiguringControls())
{
mEmulationFragment.stopConfiguringControls();
}
else
{
mEmulationFragment.startConfiguringControls();
}
}
@ -701,20 +663,18 @@ public final class EmulationActivity extends AppCompatActivity
}
builder.setNeutralButton(getString(R.string.emulation_toggle_all), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
EmulationFragment emulationFragment = (EmulationFragment) getSupportFragmentManager()
.findFragmentByTag(EmulationFragment.FRAGMENT_TAG);
emulationFragment.toggleInputOverlayVisibility();
public void onClick(DialogInterface dialogInterface, int i)
{
mEmulationFragment.toggleInputOverlayVisibility();
}
});
builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
public void onClick(DialogInterface dialogInterface, int i)
{
editor.apply();
EmulationFragment emulationFragment = (EmulationFragment) getSupportFragmentManager()
.findFragmentByTag(EmulationFragment.FRAGMENT_TAG);
emulationFragment.refreshInputOverlay();
mEmulationFragment.refreshInputOverlay();
}
});
@ -759,9 +719,7 @@ public final class EmulationActivity extends AppCompatActivity
editor.putInt("controlScale", seekbar.getProgress());
editor.apply();
EmulationFragment emulationFragment = (EmulationFragment) getSupportFragmentManager()
.findFragmentByTag(EmulationFragment.FRAGMENT_TAG);
emulationFragment.refreshInputOverlay();
mEmulationFragment.refreshInputOverlay();
}
});
@ -788,9 +746,7 @@ public final class EmulationActivity extends AppCompatActivity
public void onClick(DialogInterface dialogInterface, int i) {
editor.apply();
EmulationFragment emulationFragment = (EmulationFragment) getSupportFragmentManager()
.findFragmentByTag(EmulationFragment.FRAGMENT_TAG);
emulationFragment.refreshInputOverlay();
mEmulationFragment.refreshInputOverlay();
Toast.makeText(getApplication(), R.string.emulation_controller_changed, Toast.LENGTH_SHORT).show();
}

View File

@ -1,5 +1,6 @@
package org.dolphinemu.dolphinemu.fragments;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
@ -14,15 +15,12 @@ import android.widget.Button;
import org.dolphinemu.dolphinemu.NativeLibrary;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.activities.EmulationActivity;
import org.dolphinemu.dolphinemu.overlay.InputOverlay;
import org.dolphinemu.dolphinemu.utils.Log;
public final class EmulationFragment extends Fragment implements SurfaceHolder.Callback
{
public static final String FRAGMENT_TAG = "emulation_fragment";
private static final String ARG_GAME_PATH = "game_path";
private SharedPreferences mPreferences;
private Surface mSurface;
@ -31,18 +29,22 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
private Thread mEmulationThread;
private boolean mEmulationStarted;
private boolean mEmulationRunning;
private String mGamePath;
private final EmulationState mEmulationState = new EmulationState();
public static EmulationFragment newInstance(String path)
@Override
public void onAttach(Context context)
{
EmulationFragment fragment = new EmulationFragment();
super.onAttach(context);
Bundle arguments = new Bundle();
arguments.putString(ARG_GAME_PATH, path);
fragment.setArguments(arguments);
return fragment;
if (context instanceof EmulationActivity)
{
NativeLibrary.setEmulationActivity((EmulationActivity) context);
}
else
{
throw new IllegalStateException("EmulationFragment must have EmulationActivity parent");
}
}
/**
@ -67,38 +69,20 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
{
View contents = inflater.inflate(R.layout.fragment_emulation, container, false);
SurfaceView surfaceView = (SurfaceView) contents.findViewById(R.id.surface_emulation);
mInputOverlay = (InputOverlay) contents.findViewById(R.id.surface_input_overlay);
SurfaceView surfaceView = contents.findViewById(R.id.surface_emulation);
surfaceView.getHolder().addCallback(this);
// If the input overlay was previously disabled, then don't show it.
mInputOverlay = contents.findViewById(R.id.surface_input_overlay);
if (mInputOverlay != null)
{
// If the input overlay was previously disabled, then don't show it.
if (!mPreferences.getBoolean("showInputOverlay", true))
{
mInputOverlay.setVisibility(View.GONE);
}
}
if (savedInstanceState == null)
{
mEmulationThread = new Thread(mEmulationRunner);
}
else
{
// Likely a rotation occurred.
// TODO Pass native code the Surface, which will have been recreated, from surfaceChanged()
// TODO Also, write the native code that will get the video backend to accept the new Surface as one of its own.
}
return contents;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
Button doneButton = (Button) view.findViewById(R.id.done_control_config);
Button doneButton = contents.findViewById(R.id.done_control_config);
if (doneButton != null)
{
doneButton.setOnClickListener(new View.OnClickListener()
@ -110,29 +94,29 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
}
});
}
}
@Override
public void onStart()
{
super.onStart();
startEmulation();
// The new Surface created here will get passed to the native code via onSurfaceChanged.
return contents;
}
@Override
public void onStop()
{
pauseEmulation();
super.onStop();
}
@Override
public void onDestroyView()
public void onDetach()
{
super.onDestroyView();
if (getActivity().isFinishing() && mEmulationStarted)
{
NativeLibrary.StopEmulation();
}
NativeLibrary.clearEmulationActivity();
super.onDetach();
}
public void setGamePath(String setPath)
{
this.mGamePath = setPath;
}
public void toggleInputOverlayVisibility()
@ -171,6 +155,12 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
Log.debug("[EmulationFragment] Surface changed. Resolution: " + width + "x" + height);
if (mEmulationState.isPaused())
{
NativeLibrary.UnPauseEmulation();
}
mSurface = holder.getSurface();
NativeLibrary.SurfaceChanged(mSurface);
}
@ -181,45 +171,57 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
Log.debug("[EmulationFragment] Surface destroyed.");
NativeLibrary.SurfaceDestroyed();
if (mEmulationRunning)
if (mEmulationState.isRunning())
{
pauseEmulation();
}
}
private void startEmulation()
public void startEmulation()
{
if (!mEmulationStarted)
synchronized (mEmulationState)
{
Log.debug("[EmulationFragment] Starting emulation thread.");
if (mEmulationState.isStopped())
{
Log.debug("[EmulationFragment] Starting emulation thread.");
mEmulationThread.start();
mEmulationThread = new Thread(mEmulationRunner, "NativeEmulation");
mEmulationThread.start();
// The thread will call mEmulationState.run()
}
else if (mEmulationState.isPaused())
{
Log.debug("[EmulationFragment] Resuming emulation.");
NativeLibrary.UnPauseEmulation();
mEmulationState.run();
}
else
{
Log.debug("[EmulationFragment] Bug, startEmulation called while running.");
}
}
else
}
public void stopEmulation() {
synchronized (mEmulationState)
{
Log.debug("[EmulationFragment] Resuming emulation.");
NativeLibrary.UnPauseEmulation();
if (!mEmulationState.isStopped())
{
NativeLibrary.StopEmulation();
mEmulationState.stop();
}
}
mEmulationRunning = true;
}
private void pauseEmulation()
{
Log.debug("[EmulationFragment] Pausing emulation.");
synchronized (mEmulationState)
{
Log.debug("[EmulationFragment] Pausing emulation.");
NativeLibrary.PauseEmulation();
mEmulationRunning = false;
}
/**
* Called by containing activity to tell the Fragment emulation is already stopping,
* so it doesn't try to stop emulation on its way to the garbage collector.
*/
public void notifyEmulationStopped()
{
mEmulationStarted = false;
mEmulationRunning = false;
NativeLibrary.PauseEmulation();
mEmulationState.pause();
}
}
private Runnable mEmulationRunner = new Runnable()
@ -227,18 +229,17 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
@Override
public void run()
{
mEmulationRunning = true;
mEmulationStarted = true;
// Busy-wait for surface to be set
while (mSurface == null) {}
while (mSurface == null)
if (!mEmulationRunning)
return;
Log.info("[EmulationFragment] Starting emulation: " + mSurface);
synchronized (mEmulationState)
{
Log.info("[EmulationFragment] Starting emulation: " + mSurface);
mEmulationState.run();
}
// Start emulation using the provided Surface.
String path = getArguments().getString(ARG_GAME_PATH);
NativeLibrary.Run(path);
NativeLibrary.Run(mGamePath);
}
};
@ -258,4 +259,50 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
{
return mInputOverlay.isInEditMode();
}
private static class EmulationState
{
private enum State
{
STOPPED, RUNNING, PAUSED
}
private State state;
EmulationState()
{
// Starting state is stopped.
state = State.STOPPED;
}
public boolean isStopped()
{
return state == State.STOPPED;
}
public boolean isRunning()
{
return state == State.RUNNING;
}
public boolean isPaused()
{
return state == State.PAUSED;
}
public void run()
{
state = State.RUNNING;
}
public void pause()
{
state = State.PAUSED;
}
public void stop()
{
state = State.STOPPED;
}
}
}

View File

@ -5,11 +5,11 @@
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/frame_content">
<FrameLayout
android:id="@+id/frame_emulation_fragment"
<fragment
android:id="@+id/fragment_emulation"
android:name="org.dolphinemu.dolphinemu.fragments.EmulationFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible"/>
android:layout_height="match_parent"/>
<org.dolphinemu.dolphinemu.ui.NVidiaShieldWorkaroundView
android:layout_width="match_parent"

View File

@ -3,11 +3,11 @@
android:layout_height="match_parent"
android:id="@+id/frame_content">
<FrameLayout
android:id="@+id/frame_emulation_fragment"
<fragment
android:id="@+id/fragment_emulation"
android:name="org.dolphinemu.dolphinemu.fragments.EmulationFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible"/>
android:layout_height="match_parent"/>
<ImageView
android:layout_width="match_parent"

View File

@ -58,7 +58,6 @@ std::string s_set_userpath;
jclass s_jni_class;
jmethodID s_jni_method_alert;
jmethodID s_jni_method_end;
// The Core only supports using a single Host thread.
// If multiple threads want to call host functions then they need to queue
@ -732,7 +731,6 @@ Java_org_dolphinemu_dolphinemu_NativeLibrary_CacheClassesAndMethods(JNIEnv* env,
// Source/Android/app/build/intermediates/classes/arm/debug/org/dolphinemu/dolphinemu/NativeLibrary.class
s_jni_method_alert =
env->GetStaticMethodID(s_jni_class, "displayAlertMsg", "(Ljava/lang/String;)V");
s_jni_method_end = env->GetStaticMethodID(s_jni_class, "endEmulationActivity", "()V");
}
// Surface Handling
@ -816,9 +814,6 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run(JNIEnv*
ANativeWindow_release(s_surf);
s_surf = nullptr;
}
// Execute the Java method.
env->CallStaticVoidMethod(s_jni_class, s_jni_method_end);
}
#ifdef __cplusplus