应用程序不要求许可访问MacOS 10.14 Mojave中的麦克风

时间:2019-04-17 12:23:35

标签: microphone privacy macos-mojave portaudio

我是开发用于飞行模拟的应用程序的团队的一部分。这些应用程序之一也正在MacOS上运行,并且需要访问麦克风才能与在线虚拟空中交通管制进行通信。 从MacOS 10.14开始,麦克风访问不再起作用。它曾经可以在任何早期版本的MacOS中完美运行。我已经读过,从10.14开始,MacOS会询问用户权限,但是此对话框永远不会出现。使用portaudio作为音频库,可以成功打开音频输入流。没有警告,没有错误,也没有指出问题。它只是不返回任何音频输入。

我了解到,许多其他项目(甚至是商业项目)也存在类似的问题。但是我找不到他们最终如何解决的。 我知道该应用捆绑包需要在Info.plist中添加特定键

<key>NSMicrophoneUsageDescription</key>
<string>This application needs access to your Microphone virtual ATC.</string>

但这没有帮助。 其他人则建议添加<key>CFBundleDisplayName</key>可以解决此问题。但事实并非如此。

值得一提的是,该应用程序未签名。这是一个业余时间的业余项目,我不愿意为苹果的代码签名过程每年花费99美元。可能是罪魁祸首吗?

欢迎任何建议或想法。

作为临时的解决方法,我们告诉用户通过控制台从应用程序捆绑包中启动二进制文件,从而解决了该问题。但是我也想为应用程序捆绑包本身正确修复它。

3 个答案:

答案 0 :(得分:0)

对我有用的解决方案是reset PRAM。其中存储了一些系统设置。

在启动过程中,按住Command + Option + P +R。计算机将重新启动,并且当您第二次听到启动声音时,您可能会松开。

再次运行该应用,然后将显示权限对话框。

答案 1 :(得分:0)

从10.14开始,MacOS将询问用户权限,但此对话框永远不会出现

这正是我的问题。 Mac Mojave和Catalina中有一个严重的错误。

就我而言,我的客户在Catalina上面临此问题。我通过JNLP启动JAR。由于MacOS最新版本中与安全相关的更改,应用程序应该获得访问麦克风,屏幕记录,全盘访问等的权限。理想情况下,对于Java应用程序(通过JNLP运行),应该寻求许可的是Java。但是,这不会发生。我的用户没有看到询问麦克风许可的对话框。他们甚至尝试使用最新的Java版本8。仍然没有运气。我挣扎了很多天。最后,这对我有用:

我检测OS是否为MacOS Cataline,如果是,我再次使用javaws启动同一JNLP。为了避免递归,只有在我第一次检测到applet时才执行此操作。这是代码:

以下是完整的代码:

private boolean IsAlreadyRunning()
{
    System.out.println("Checking if applet already running by opening applet locked file");
    try
    {
        file_locked_by_applet=new File("my_java_application.lock");
        // createNewFile atomically creates a new, empty file ... if and only if a file with this name does not yet exist. 

        System.out.println("Locked file path: " + file_locked_by_applet.getAbsolutePath());

        if (file_locked_by_applet.createNewFile())
        {
            System.out.println("Opened applet locked file successfully");
            file_locked_by_applet.deleteOnExit();
            return false;
        }

        System.out.println("Cannot open applet locked file. Applet might be already running.");
        return true;
    }
    catch (IOException e)
    {
        System.out.println("Exception while opening applet locked file. Applet might be already running.");
        e.printStackTrace();
        return true;
    }
}

private boolean IsOSMacCatalina()
{
    System.out.println("Checking if current operating system is MacOS Catalina");
    String OS = System.getProperty("os.name").toLowerCase();
    String OSVersion = System.getProperty("os.version").toLowerCase();      
    String OSArch = System.getProperty("os.arch").toLowerCase();

    System.out.println("OS detected: " + OS);
    System.out.println("OS version detected: " + OSVersion);
    System.out.println("OS arch detected: " + OSArch);

    if (OS.contains ("mac os") && OSVersion.contains("10.15"))
    {   
            System.out.println("Operating system: Mac Catalina detected");
            return true;
    }

    System.out.println("Operating system is not Mac Catalina");
    return false;

}

// Method that first gets invoked by applet at the beginning
public void start() 
{
    super.start();
    System.out.println("Starting applet here");
    System.out.println("JNLP file name: " + System.getProperty("jnlpx.origFilenameArg"));
    System.out.println("JVM command line: " + ManagementFactory.getRuntimeMXBean().getInputArguments());

if ((!IsOSMacCatalina()) || IsAlreadyRunning())
{
    System.out.println("Either OS is not Catalina or applet is already launched with bash and javaws. Continuing with applet...");
}
else
{
    try
    {
        System.out.println("Applet running first time on Mac Catalina. Starting again with bash and javaws");

        // "javaws -wait" causes javaws to start java process and wait for it to exit
        String javawsCommandLine = "javaws -wait \"" + System.getProperty("jnlpx.origFilenameArg").replace("\\","/") + "\"";
        System.out.println("bash javaws command line to run: " + javawsCommandLine);
        // String[] args = new String[] {"bash", "-c", javawsCommandLine}; // Works on Windows where Bash is installed
        String[] args = new String[] {"/bin/bash", "-c", javawsCommandLine};
        System.out.println("---\nStarting bash javaws process withh args:");
        for (String arg: args)
            System.out.println(arg);
        System.out.println("\n---");

        // Runtime.getRuntime() discouraged. Hence we using ProcessBuilder
        // Process proc = Runtime.getRuntime().exec("bash -c \"" + javawsCommandLine + "\"");

        Process proc = new ProcessBuilder(args).start();

        System.out.println("Waiting for bash process to finish");
        proc.waitFor();
        System.out.println("Bash process finished. Deleting instance locked file");
        file_locked_by_applet.delete();
        System.out.println("Stopping applet here");
    }
    catch (java.io.IOException e) 
    {
        e.printStackTrace();
    }
    catch (java.lang.InterruptedException e)
    {
        e.printStackTrace();
    }
    return;             
}

答案 2 :(得分:0)

我最后使用了以下两个片段。第一个是积极询问该应用程序是否具有访问权限。如果无法确定(因为之前从未询问过用户),则调用requestAccess()以主动询问用户并异步处理响应。这两个函数都处理目标MacOS为<10.14的情况,在这种情况下,假定授权为真。

CMacOSMicrophoneAccess::AuthorizationStatus CMacOSMicrophoneAccess::getAuthorizationStatus()
{
    if (@available(macOS 10.14, *))
    {
        NSString *mediaType = AVMediaTypeAudio;
        AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:mediaType];
        if (authStatus == AVAuthorizationStatusAuthorized)
        {
            return AuthorizationStatus::Authorized;
        }
        else if (authStatus == AVAuthorizationStatusNotDetermined)
        {
            return AuthorizationStatus::NotDetermined;
        }
        return AuthorizationStatus::Denied;
    }
    else
    {
        return AuthorizationStatus::Authorized;
    }
}

void CMacOSMicrophoneAccess::requestAccess()
{
    if (@available(macOS 10.14, *))
    {
        NSString *mediaType = AVMediaTypeAudio;
        [AVCaptureDevice requestAccessForMediaType:mediaType completionHandler: ^ (BOOL granted)
        {
            emit permissionRequestAnswered(granted);
        }];
    }
    else
    {
        emit permissionRequestAnswered(true);
    }

}