Make getUserMedia fail if system mic permission is denied on macOS.

* Fail with NotAllowedError.
* Introduce new internal error, system permission denied, to be able to have a
  custom error message in the error.
* Invoke the call to check system authorization status using performSelector to
  avoid availability compile errors due to only available on iOS with the SDK we
  currently use (10.12).
* Explicitly request system authorization. This is needed so that the system
  gives the correct information when checking the authorization state later.
  Otherwise it incorrectly keeps returning "not determined" until browser
  restart.

Bug: 925334
Change-Id: Ieecd2e592b74003ecbd29a6fdc4529f2bc88eab8
Reviewed-on: https://chromium-review.googlesource.com/c/1426707
Commit-Queue: Henrik Grunell <grunell@chromium.org>
Reviewed-by: Elly Fong-Jones <ellyjones@chromium.org>
Reviewed-by: Tommi <tommi@chromium.org>
Reviewed-by: Dominick Ng <dominickn@chromium.org>
Reviewed-by: Dmitry Gozman <dgozman@chromium.org>
Reviewed-by: Guido Urdaneta <guidou@chromium.org>
Reviewed-by: Kentaro Hara <haraken@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#627620}(cherry picked from commit 5c79e193f133dce2fbfadf473a1d9fecfe323f70)
Reviewed-on: https://chromium-review.googlesource.com/c/1449661
Reviewed-by: Henrik Grunell <grunell@chromium.org>
Cr-Commit-Position: refs/branch-heads/3683@{#117}
Cr-Branched-From: e51029943e0a38dd794b73caaf6373d5496ae783-refs/heads/master@{#625896}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 6ebafc0..1b12e3b 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -2771,6 +2771,8 @@
       "media/webrtc/desktop_media_picker_factory_impl.h",
       "media/webrtc/display_media_access_handler.cc",
       "media/webrtc/display_media_access_handler.h",
+      "media/webrtc/system_media_capture_permissions_mac.h",
+      "media/webrtc/system_media_capture_permissions_mac.mm",
       "media/webrtc/tab_desktop_media_list.cc",
       "media/webrtc/tab_desktop_media_list.h",
       "media_galleries/chromeos/mtp_device_delegate_impl_chromeos.cc",
@@ -3447,6 +3449,7 @@
     libs += [
       "Accelerate.framework",
       "AudioUnit.framework",
+      "AVFoundation.framework",
       "DiskArbitration.framework",
       "IOKit.framework",
       "ImageCaptureCore.framework",
diff --git a/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc b/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc
index 9a128a0..475cef5 100644
--- a/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc
+++ b/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc
@@ -9,6 +9,7 @@
 
 #include "base/metrics/field_trial.h"
 #include "base/task/post_task.h"
+#include "build/build_config.h"
 #include "chrome/browser/media/webrtc/media_stream_device_permissions.h"
 #include "chrome/browser/media/webrtc/media_stream_devices_controller.h"
 #include "chrome/browser/permissions/permission_manager.h"
@@ -31,9 +32,12 @@
 #include "chrome/browser/media/webrtc/screen_capture_infobar_delegate_android.h"
 #include "chrome/browser/permissions/permission_uma_util.h"
 #include "chrome/browser/permissions/permission_util.h"
-
 #endif  // defined(OS_ANDROID)
 
+#if defined(OS_MACOSX)
+#include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
+#endif
+
 using content::BrowserThread;
 
 using RepeatingMediaResponseCallback =
@@ -123,6 +127,25 @@
   }
 #endif  // defined(OS_ANDROID)
 
+#if defined(OS_MACOSX)
+  // Fail if access is denied in system permission. Note that if permission is
+  // not yet determined, we don't fail. If all other permissions are OK, we'll
+  // allow access. The reason for doing this is that if the permission is not
+  // yet determined, the user will get an async alert dialog and we don't want
+  // to wait on that before resolving getUserMedia. getUserMedia will succeed,
+  // but audio will be silent until user allows permission in the dialog. If the
+  // user denies permission audio will continue being silent but they will
+  // likely understand why since they denied it.
+  // TODO(https://crbug.com/885184): Handle the not determined case better.
+  if (request.audio_type == blink::MEDIA_DEVICE_AUDIO_CAPTURE &&
+      SystemAudioCapturePermissionIsDisallowed()) {
+    std::move(callback).Run(blink::MediaStreamDevices(),
+                            blink::MEDIA_DEVICE_SYSTEM_PERMISSION_DENIED,
+                            nullptr);
+    return;
+  }
+#endif  // defined(OS_MACOSX)
+
   RequestsQueue& queue = pending_requests_[web_contents];
   queue.push_back(PendingAccessRequest(
       request, base::AdaptCallbackForRepeating(std::move(callback))));
@@ -207,6 +230,17 @@
   if (queue.empty())
     return;
 
+#if defined(OS_MACOSX)
+  // If the request was approved, trigger system user dialog if needed. We need
+  // to do this explicitly so that the system gives the correct information
+  // about the permission state in future requests
+  // (see PermissionBubbleMediaAccessHandler::HandleRequest).
+  if (queue.front().request.audio_type == blink::MEDIA_DEVICE_AUDIO_CAPTURE &&
+      result == blink::MEDIA_DEVICE_OK) {
+    EnsureSystemAudioCapturePermission();
+  }
+#endif  // defined(OS_MACOSX)
+
   RepeatingMediaResponseCallback callback = queue.front().callback;
   queue.pop_front();
 
diff --git a/chrome/browser/media/webrtc/system_media_capture_permissions_mac.h b/chrome/browser/media/webrtc/system_media_capture_permissions_mac.h
new file mode 100644
index 0000000..4e460fc
--- /dev/null
+++ b/chrome/browser/media/webrtc/system_media_capture_permissions_mac.h
@@ -0,0 +1,27 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEDIA_WEBRTC_SYSTEM_MEDIA_CAPTURE_PERMISSIONS_MAC_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_SYSTEM_MEDIA_CAPTURE_PERMISSIONS_MAC_H_
+
+// On 10.14 and above: returns true if permission is not allowed in system
+// settings, false otherwise, including if permission is not determined yet.
+// On 10.13 and below: returns false, since there are no system media capture
+// permissions.
+bool SystemAudioCapturePermissionIsDisallowed();
+
+// On 10.14 and above: if system permission is not determined, requests
+// permission. Otherwise, does nothing. When requesting permission, the OS will
+// show a user dialog and respond asynchronously. This function does not wait
+// for the response and nothing is done at the response. The reason
+// for explicitly requesting permission is that if only implicitly requesting
+// permission (when media::AUAudioInputStream::Start() calls
+// AudioOutputUnitStart()), the OS returns not determined when we ask what the
+// permission state is, even though it's actually set to something else, until
+// browser restart.
+// On 10.13 and below: does nothing, since there are no system media capture
+// permissions.
+void EnsureSystemAudioCapturePermission();
+
+#endif  // CHROME_BROWSER_MEDIA_WEBRTC_SYSTEM_MEDIA_CAPTURE_PERMISSIONS_MAC_H_
diff --git a/chrome/browser/media/webrtc/system_media_capture_permissions_mac.mm b/chrome/browser/media/webrtc/system_media_capture_permissions_mac.mm
new file mode 100644
index 0000000..a6942b9
--- /dev/null
+++ b/chrome/browser/media/webrtc/system_media_capture_permissions_mac.mm
@@ -0,0 +1,67 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Authorization functions and types are available on 10.14+.
+// To avoid availability compile errors, use performSelector invocation of
+// functions and NSInteger instead of AVAuthorizationStatus.
+// The AVAuthorizationStatus enum is defined as follows (10.14 SDK):
+// AVAuthorizationStatusNotDetermined = 0,
+// AVAuthorizationStatusRestricted    = 1,
+// AVAuthorizationStatusDenied        = 2,
+// AVAuthorizationStatusAuthorized    = 3,
+// TODO(grunell): Call functions directly and use AVAuthorizationStatus once
+// we use the 10.14 SDK.
+
+#include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
+
+#import <AVFoundation/AVFoundation.h>
+
+#include "base/logging.h"
+
+namespace {
+
+NSInteger AudioAuthorizationStatus() {
+  if (@available(macOS 10.14, *)) {
+    AVCaptureDevice* target = [AVCaptureDevice class];
+    SEL selector = @selector(authorizationStatusForMediaType:);
+    NSInteger auth_status = 0;
+    if ([target respondsToSelector:selector]) {
+      auth_status = (NSInteger)[target performSelector:selector
+                                            withObject:AVMediaTypeAudio];
+    } else {
+      DLOG(WARNING) << "authorizationStatusForMediaType could not be executed";
+    }
+    return auth_status;
+  }
+
+  NOTREACHED();
+  return 0;
+}
+
+}  // namespace
+
+bool SystemAudioCapturePermissionIsDisallowed() {
+  if (@available(macOS 10.14, *)) {
+    NSInteger auth_status = AudioAuthorizationStatus();
+    return auth_status == 1 || auth_status == 2;
+  }
+  return false;
+}
+
+void EnsureSystemAudioCapturePermission() {
+  if (@available(macOS 10.14, *)) {
+    if (AudioAuthorizationStatus() == 0) {
+      AVCaptureDevice* target = [AVCaptureDevice class];
+      SEL selector = @selector(requestAccessForMediaType:completionHandler:);
+      if ([target respondsToSelector:selector]) {
+        [target performSelector:selector
+                     withObject:AVMediaTypeAudio
+                     withObject:^(BOOL granted){
+                     }];
+      } else {
+        DLOG(WARNING) << "requestAccessForMediaType could not be executed";
+      }
+    }
+  }
+}
diff --git a/content/renderer/media/stream/user_media_processor.cc b/content/renderer/media/stream/user_media_processor.cc
index 1bdebdd..11ab9ef 100644
--- a/content/renderer/media/stream/user_media_processor.cc
+++ b/content/renderer/media/stream/user_media_processor.cc
@@ -1254,6 +1254,11 @@
       web_request.RequestFailed(
           blink::WebUserMediaRequest::Error::kKillSwitchOn);
       return;
+    case blink::MEDIA_DEVICE_SYSTEM_PERMISSION_DENIED:
+      web_request.RequestFailed(
+          blink::WebUserMediaRequest::Error::kSystemPermissionDenied,
+          "Permission denied by system");
+      return;
   }
   NOTREACHED();
   web_request.RequestFailed(
diff --git a/third_party/blink/common/mediastream/media_stream_mojom_traits.cc b/third_party/blink/common/mediastream/media_stream_mojom_traits.cc
index 67d0e4b..3dd5f44 100644
--- a/third_party/blink/common/mediastream/media_stream_mojom_traits.cc
+++ b/third_party/blink/common/mediastream/media_stream_mojom_traits.cc
@@ -114,6 +114,8 @@
       return blink::mojom::MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN;
     case blink::MediaStreamRequestResult::MEDIA_DEVICE_KILL_SWITCH_ON:
       return blink::mojom::MediaStreamRequestResult::KILL_SWITCH_ON;
+    case blink::MediaStreamRequestResult::MEDIA_DEVICE_SYSTEM_PERMISSION_DENIED:
+      return blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED;
     default:
       break;
   }
@@ -178,6 +180,10 @@
     case blink::mojom::MediaStreamRequestResult::KILL_SWITCH_ON:
       *out = blink::MediaStreamRequestResult::MEDIA_DEVICE_KILL_SWITCH_ON;
       return true;
+    case blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED:
+      *out = blink::MediaStreamRequestResult::
+          MEDIA_DEVICE_SYSTEM_PERMISSION_DENIED;
+      return true;
   }
   NOTREACHED();
   return false;
diff --git a/third_party/blink/public/common/mediastream/media_stream_request.h b/third_party/blink/public/common/mediastream/media_stream_request.h
index 946c049..d38d218 100644
--- a/third_party/blink/public/common/mediastream/media_stream_request.h
+++ b/third_party/blink/public/common/mediastream/media_stream_request.h
@@ -74,6 +74,7 @@
   MEDIA_DEVICE_NOT_SUPPORTED = 12,
   MEDIA_DEVICE_FAILED_DUE_TO_SHUTDOWN = 13,
   MEDIA_DEVICE_KILL_SWITCH_ON = 14,
+  MEDIA_DEVICE_SYSTEM_PERMISSION_DENIED = 15,
   NUM_MEDIA_REQUEST_RESULTS
 };
 
diff --git a/third_party/blink/public/mojom/mediastream/media_stream.mojom b/third_party/blink/public/mojom/mediastream/media_stream.mojom
index 18ef943..300ebda 100644
--- a/third_party/blink/public/mojom/mediastream/media_stream.mojom
+++ b/third_party/blink/public/mojom/mediastream/media_stream.mojom
@@ -37,7 +37,8 @@
   TRACK_START_FAILURE_VIDEO,
   NOT_SUPPORTED,
   FAILED_DUE_TO_SHUTDOWN,
-  KILL_SWITCH_ON
+  KILL_SWITCH_ON,
+  SYSTEM_PERMISSION_DENIED
 };
 
 // See public/common/media_stream_request.h.
diff --git a/third_party/blink/public/web/web_user_media_request.h b/third_party/blink/public/web/web_user_media_request.h
index 0196f53..a5fd121 100644
--- a/third_party/blink/public/web/web_user_media_request.h
+++ b/third_party/blink/public/web/web_user_media_request.h
@@ -57,7 +57,8 @@
     kCapture,
     kTrackStart,
     kFailedDueToShutdown,
-    kKillSwitchOn
+    kKillSwitchOn,
+    kSystemPermissionDenied
   };
 
   enum class MediaType {
diff --git a/third_party/blink/renderer/modules/mediastream/user_media_request.cc b/third_party/blink/renderer/modules/mediastream/user_media_request.cc
index 8a2ca93..367f358 100644
--- a/third_party/blink/renderer/modules/mediastream/user_media_request.cc
+++ b/third_party/blink/renderer/modules/mediastream/user_media_request.cc
@@ -596,6 +596,7 @@
     case WebUserMediaRequest::Error::kInvalidState:
     case WebUserMediaRequest::Error::kFailedDueToShutdown:
     case WebUserMediaRequest::Error::kKillSwitchOn:
+    case WebUserMediaRequest::Error::kSystemPermissionDenied:
       exception_code = DOMExceptionCode::kNotAllowedError;
       break;
     case WebUserMediaRequest::Error::kDevicesNotFound: