Skip to content

Commit

Permalink
IOS/USB: Add a warning about RECORD_AUDIO permission
Browse files Browse the repository at this point in the history
  • Loading branch information
sepalani committed Jul 9, 2024
1 parent 184fcc4 commit d951dfa
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -15,13 +16,15 @@
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");

Expand All @@ -36,4 +39,9 @@ public static Context getAppContext()
{
return application.getApplicationContext();
}

public static Activity getAppActivity()
{
return sActivityTracker.getCurrentActivity();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@

package org.dolphinemu.dolphinemu.features.settings.ui.viewholder

import android.Manifest
import android.app.Activity
import android.content.pm.PackageManager
import android.os.Build
import android.view.View
import android.widget.CompoundButton
import org.dolphinemu.dolphinemu.databinding.ListItemSettingSwitchBinding
Expand Down Expand Up @@ -63,13 +60,9 @@ class SwitchSettingViewHolder(
}

if (setting.setting === BooleanSetting.MAIN_EMULATE_WII_SPEAK && isChecked) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
itemView.context.checkSelfPermission(Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
val settingsActivity = itemView.context as Activity
settingsActivity.requestPermissions(
arrayOf(Manifest.permission.RECORD_AUDIO),
PermissionsHandler.REQUEST_CODE_RECORD_AUDIO)
if (!PermissionsHandler.hasRecordAudioPermission(itemView.context)) {
val settingsActivity = itemView.context as Activity
PermissionsHandler.requestRecordAudioPermission(settingsActivity)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ import android.os.Bundle
class ActivityTracker : ActivityLifecycleCallbacks {
val resumedActivities = HashSet<Activity>()
var backgroundExecutionAllowed = false
var currentActivity : Activity? = null
private set

override fun onActivityCreated(activity: Activity, bundle: Bundle?) {}
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
currentActivity = activity
}

override fun onActivityStarted(activity: Activity) {}

Expand All @@ -32,7 +36,11 @@ class ActivityTracker : ActivityLifecycleCallbacks {

override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}

override fun onActivityDestroyed(activity: Activity) {}
override fun onActivityDestroyed(activity: Activity) {
if (currentActivity === activity) {
currentActivity = null
}
}

companion object {
@JvmStatic
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -11,6 +12,11 @@
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
{
Expand Down Expand Up @@ -53,4 +59,32 @@ public static boolean isWritePermissionDenied()
{
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);
}
}
2 changes: 2 additions & 0 deletions Source/Android/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -927,4 +927,6 @@ It can efficiently compress both junk data and encrypted Wii data.

<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>
30 changes: 30 additions & 0 deletions Source/Android/jni/AndroidCommon/IDCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ static jclass s_core_device_control_class;
static jfieldID s_core_device_control_pointer;
static jmethodID s_core_device_control_constructor;

static jclass s_permission_handler_class;
static jmethodID s_permission_handler_has_record_audio_permission;
static jmethodID s_permission_handler_request_record_audio_permission;

static jmethodID s_runnable_run;

namespace IDCache
Expand Down Expand Up @@ -512,6 +516,21 @@ jmethodID GetCoreDeviceControlConstructor()
return s_core_device_control_constructor;
}

jclass GetPermissionHandlerClass()
{
return s_permission_handler_class;
}

jmethodID GetPermissionHandlerHasRecordAudioPermission()
{
return s_permission_handler_has_record_audio_permission;
}

jmethodID GetPermissionHandlerRequestRecordAudioPermission()
{
return s_permission_handler_request_record_audio_permission;
}

jmethodID GetRunnableRun()
{
return s_runnable_run;
Expand Down Expand Up @@ -724,6 +743,16 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
"(Lorg/dolphinemu/dolphinemu/features/input/model/CoreDevice;J)V");
env->DeleteLocalRef(core_device_control_class);

const jclass permission_handler_class =
env->FindClass("org/dolphinemu/dolphinemu/utils/PermissionsHandler");
s_permission_handler_class =
reinterpret_cast<jclass>(env->NewGlobalRef(permission_handler_class));
s_permission_handler_has_record_audio_permission = env->GetStaticMethodID(
permission_handler_class, "hasRecordAudioPermission", "(Landroid/content/Context;)Z");
s_permission_handler_request_record_audio_permission = env->GetStaticMethodID(
permission_handler_class, "requestRecordAudioPermission", "(Landroid/app/Activity;)V");
env->DeleteLocalRef(permission_handler_class);

const jclass runnable_class = env->FindClass("java/lang/Runnable");
s_runnable_run = env->GetMethodID(runnable_class, "run", "()V");
env->DeleteLocalRef(runnable_class);
Expand Down Expand Up @@ -761,5 +790,6 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved)
env->DeleteGlobalRef(s_numeric_setting_class);
env->DeleteGlobalRef(s_core_device_class);
env->DeleteGlobalRef(s_core_device_control_class);
env->DeleteGlobalRef(s_permission_handler_class);
}
}
4 changes: 4 additions & 0 deletions Source/Android/jni/AndroidCommon/IDCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ jclass GetCoreDeviceControlClass();
jfieldID GetCoreDeviceControlPointer();
jmethodID GetCoreDeviceControlConstructor();

jclass GetPermissionHandlerClass();
jmethodID GetPermissionHandlerHasRecordAudioPermission();
jmethodID GetPermissionHandlerRequestRecordAudioPermission();

jmethodID GetRunnableRun();

} // namespace IDCache
17 changes: 17 additions & 0 deletions Source/Core/Core/IOS/USB/Emulated/Microphone.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
#include <Objbase.h>
#endif

#ifdef ANDROID
#include "jni/AndroidCommon/IDCache.h"
#endif

namespace IOS::HLE::USB
{
Microphone::Microphone(const WiiSpeakState& sampler) : m_sampler(sampler)
Expand Down Expand Up @@ -117,6 +121,19 @@ void Microphone::StreamStart()
Common::ScopeGuard sync_event_guard([&sync_event] { sync_event.Set(); });
#endif

#ifdef ANDROID
JNIEnv* env = IDCache::GetEnvForThread();
if (jboolean result = env->CallStaticBooleanMethod(
IDCache::GetPermissionHandlerClass(),
IDCache::GetPermissionHandlerHasRecordAudioPermission(), nullptr);
result == JNI_FALSE)
{
env->CallStaticVoidMethod(IDCache::GetPermissionHandlerClass(),
IDCache::GetPermissionHandlerRequestRecordAudioPermission(),
nullptr);
}
#endif

cubeb_stream_params params{};
params.format = CUBEB_SAMPLE_S16LE;
params.rate = SAMPLING_RATE;
Expand Down

0 comments on commit d951dfa

Please sign in to comment.