Android: Add emulated Wii Speak

This commit is contained in:
Sepalani
2024-05-12 23:29:00 +04:00
parent a85ffc116e
commit dbc09bfb0d
12 changed files with 153 additions and 2 deletions

View File

@ -28,6 +28,9 @@
<uses-permission
android:name="android.permission.VIBRATE"
android:required="false"/>
<uses-permission
android:name="android.permission.RECORD_AUDIO"
android:required="false"/>
<application
android:name=".DolphinApplication"

View File

@ -2,6 +2,7 @@
package org.dolphinemu.dolphinemu;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.hardware.usb.UsbManager;
@ -15,13 +16,15 @@ import org.dolphinemu.dolphinemu.utils.VolleyUtil;
public class DolphinApplication extends Application
{
private static DolphinApplication application;
private static ActivityTracker sActivityTracker;
@Override
public void onCreate()
{
super.onCreate();
application = this;
registerActivityLifecycleCallbacks(new ActivityTracker());
sActivityTracker = new ActivityTracker();
registerActivityLifecycleCallbacks(sActivityTracker);
VolleyUtil.init(getApplicationContext());
System.loadLibrary("main");
@ -36,4 +39,9 @@ public class DolphinApplication extends Application
{
return application.getApplicationContext();
}
public static Activity getAppActivity()
{
return sActivityTracker.getCurrentActivity();
}
}

View File

@ -238,6 +238,18 @@ enum class BooleanSetting(
"EmulateInfinityBase",
false
),
MAIN_EMULATE_WII_SPEAK(
Settings.FILE_DOLPHIN,
Settings.SECTION_EMULATED_USB_DEVICES,
"EmulateWiiSpeak",
false
),
MAIN_WII_SPEAK_CONNECTED(
Settings.FILE_DOLPHIN,
Settings.SECTION_EMULATED_USB_DEVICES,
"WiiSpeakConnected",
false
),
MAIN_SHOW_GAME_TITLES(
Settings.FILE_DOLPHIN,
Settings.SECTION_INI_ANDROID,
@ -924,7 +936,8 @@ enum class BooleanSetting(
MAIN_DSP_JIT,
MAIN_TIME_TRACKING,
MAIN_EMULATE_SKYLANDER_PORTAL,
MAIN_EMULATE_INFINITY_BASE
MAIN_EMULATE_INFINITY_BASE,
MAIN_EMULATE_WII_SPEAK
)
private val NOT_RUNTIME_EDITABLE: Set<BooleanSetting> =
HashSet(listOf(*NOT_RUNTIME_EDITABLE_ARRAY))

View File

@ -15,6 +15,7 @@ import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.color.MaterialColors
import com.google.android.material.datepicker.CalendarConstraints
@ -59,6 +60,9 @@ class SettingsAdapter(
val settings: Settings?
get() = fragmentView.settings
val fragmentActivity: FragmentActivity
get() = fragmentView.fragmentActivity
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {

View File

@ -896,6 +896,22 @@ class SettingsFragmentPresenter(
0
)
)
sl.add(
SwitchSetting(
context,
BooleanSetting.MAIN_EMULATE_WII_SPEAK,
R.string.emulate_wii_speak,
0
)
)
sl.add(
InvertedSwitchSetting(
context,
BooleanSetting.MAIN_WII_SPEAK_CONNECTED,
R.string.disconnect_wii_speak,
0
)
)
}
private fun addAdvancedSettings(sl: ArrayList<SettingsItem>) {

View File

@ -2,6 +2,7 @@
package org.dolphinemu.dolphinemu.features.settings.ui.viewholder
import android.app.Activity
import android.view.View
import android.widget.CompoundButton
import org.dolphinemu.dolphinemu.databinding.ListItemSettingSwitchBinding
@ -10,6 +11,7 @@ import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem
import org.dolphinemu.dolphinemu.features.settings.model.view.SwitchSetting
import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization
import org.dolphinemu.dolphinemu.utils.PermissionsHandler
import java.io.File
import java.util.*
@ -57,6 +59,13 @@ class SwitchSettingViewHolder(
binding.settingSwitch.isEnabled = false
}
if (setting.setting === BooleanSetting.MAIN_EMULATE_WII_SPEAK && isChecked) {
if (!PermissionsHandler.hasRecordAudioPermission(itemView.context)) {
val currentActivity = adapter.fragmentActivity as Activity
PermissionsHandler.requestRecordAudioPermission(currentActivity)
}
}
adapter.onBooleanClick(setting, binding.settingSwitch.isChecked)
setStyle(binding.textSettingName, setting)

View File

@ -9,12 +9,15 @@ class ActivityTracker : ActivityLifecycleCallbacks {
private val resumedActivities = HashSet<Activity>()
private var backgroundExecutionAllowed = false
private var firstStart = true
var currentActivity : Activity? = null
private set
private fun isMainActivity(activity: Activity): Boolean {
return activity is MainView
}
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
currentActivity = activity
if (isMainActivity(activity)) {
firstStart = bundle == null
}
@ -26,6 +29,7 @@ class ActivityTracker : ActivityLifecycleCallbacks {
}
override fun onActivityResumed(activity: Activity) {
currentActivity = activity
resumedActivities.add(activity)
if (!backgroundExecutionAllowed && !resumedActivities.isEmpty()) {
backgroundExecutionAllowed = true
@ -34,6 +38,9 @@ class ActivityTracker : ActivityLifecycleCallbacks {
}
override fun onActivityPaused(activity: Activity) {
if (currentActivity === activity) {
currentActivity = null
}
resumedActivities.remove(activity)
if (backgroundExecutionAllowed && resumedActivities.isEmpty()) {
backgroundExecutionAllowed = false

View File

@ -2,6 +2,7 @@
package org.dolphinemu.dolphinemu.utils;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
@ -11,10 +12,16 @@ import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentActivity;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.Manifest.permission.RECORD_AUDIO;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.DolphinApplication;
import org.dolphinemu.dolphinemu.NativeLibrary;
public class PermissionsHandler
{
public static final int REQUEST_CODE_WRITE_PERMISSION = 500;
public static final int REQUEST_CODE_RECORD_AUDIO = 501;
private static boolean sWritePermissionDenied = false;
public static void requestWritePermission(final FragmentActivity activity)
@ -52,4 +59,32 @@ public class PermissionsHandler
{
return sWritePermissionDenied;
}
public static boolean hasRecordAudioPermission(Context context)
{
if (context == null)
context = DolphinApplication.getAppContext();
int hasRecordPermission = ContextCompat.checkSelfPermission(context, RECORD_AUDIO);
return hasRecordPermission == PackageManager.PERMISSION_GRANTED;
}
public static void requestRecordAudioPermission(Activity activity)
{
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
return;
if (activity == null)
{
// Calling from C++ code
activity = DolphinApplication.getAppActivity();
// Since the emulation (and cubeb) has already started, enabling the microphone permission
// now might require restarting the game to be effective. Warn the user about it.
NativeLibrary.displayAlertMsg(
activity.getString(R.string.wii_speak_permission_warning),
activity.getString(R.string.wii_speak_permission_warning_description),
false, true, false);
}
activity.requestPermissions(new String[]{RECORD_AUDIO}, REQUEST_CODE_RECORD_AUDIO);
}
}

View File

@ -944,4 +944,8 @@ It can efficiently compress both junk data and encrypted Wii data.
<string name="incompatible_figure_selected">Incompatible Figure Selected</string>
<string name="select_compatible_figure">Please select a compatible figure file</string>
<string name="emulate_wii_speak">Wii Speak</string>
<string name="disconnect_wii_speak">Mute Wii Speak</string>
<string name="wii_speak_permission_warning">Missing Microphone Permission</string>
<string name="wii_speak_permission_warning_description">Wii Speak emulation requires microphone permission. You might need to restart the game for the permission to be effective.</string>
</resources>