Check system permission for video capture and fail with correct error on Mac.

* Fail with NotAllowedError, this consolidates with audio capture error.
* Trigger system dialog explicitly when authorization is not determined. 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: 885184
Change-Id: I5c984e24877942f5924b172584d88974e5b9dd50
Reviewed-on: https://chromium-review.googlesource.com/c/1447732
Commit-Queue: Henrik Grunell <grunell@chromium.org>
Reviewed-by: Dominick Ng <dominickn@chromium.org>
Reviewed-by: Tommi <tommi@chromium.org>
Reviewed-by: Elly Fong-Jones <ellyjones@chromium.org>
Reviewed-by: Christian Fremerey <chfremer@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#628301}(cherry picked from commit f8fb175c085c0b523ea429204c759c427411a8bc)
Reviewed-on: https://chromium-review.googlesource.com/c/1451916
Reviewed-by: Henrik Grunell <grunell@chromium.org>
Cr-Commit-Position: refs/branch-heads/3683@{#153}
Cr-Branched-From: e51029943e0a38dd794b73caaf6373d5496ae783-refs/heads/master@{#625896}
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 475cef5..accd732 100644
--- a/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc
+++ b/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc
@@ -128,17 +128,21 @@
 #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.
+  // Fail if access is denied in system permissions. Note that if permissions
+  // have not yet been 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 system dialog and we
+  // don't wait on that response before resolving getUserMedia. getUserMedia
+  // will succeed, but audio/video will be silent/black until user allows
+  // permission in the dialog. If the user denies permission audio/video will
+  // continue to be silent/black but they will likely understand why since they
+  // denied access. We trigger the system dialog explicitly in
+  // OnAccessRequestResponse().
   // TODO(https://crbug.com/885184): Handle the not determined case better.
-  if (request.audio_type == blink::MEDIA_DEVICE_AUDIO_CAPTURE &&
-      SystemAudioCapturePermissionIsDisallowed()) {
+  if ((request.audio_type == blink::MEDIA_DEVICE_AUDIO_CAPTURE &&
+       SystemAudioCapturePermissionIsDisallowed()) ||
+      (request.video_type == blink::MEDIA_DEVICE_VIDEO_CAPTURE &&
+       SystemVideoCapturePermissionIsDisallowed())) {
     std::move(callback).Run(blink::MediaStreamDevices(),
                             blink::MEDIA_DEVICE_SYSTEM_PERMISSION_DENIED,
                             nullptr);
@@ -231,13 +235,15 @@
     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();
+  // If the request was approved, trigger system user dialogs if needed. We must
+  // do this explicitly so that the system gives the correct information about
+  // the permission states in future requests, see HandleRequest().
+  if (result == blink::MEDIA_DEVICE_OK) {
+    const content::MediaStreamRequest& request = queue.front().request;
+    if (request.audio_type == blink::MEDIA_DEVICE_AUDIO_CAPTURE)
+      EnsureSystemAudioCapturePermissionIsOrGetsDetermined();
+    if (request.video_type == blink::MEDIA_DEVICE_VIDEO_CAPTURE)
+      EnsureSystemVideoCapturePermissionIsOrGetsDetermined();
   }
 #endif  // defined(OS_MACOSX)
 
diff --git a/chrome/browser/media/webrtc/system_media_capture_permissions_mac.h b/chrome/browser/media/webrtc/system_media_capture_permissions_mac.h
index 4e460fc..ae201c4 100644
--- a/chrome/browser/media/webrtc/system_media_capture_permissions_mac.h
+++ b/chrome/browser/media/webrtc/system_media_capture_permissions_mac.h
@@ -10,18 +10,20 @@
 // On 10.13 and below: returns false, since there are no system media capture
 // permissions.
 bool SystemAudioCapturePermissionIsDisallowed();
+bool SystemVideoCapturePermissionIsDisallowed();
 
 // 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
+// permission (e.g. for audio 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();
+void EnsureSystemAudioCapturePermissionIsOrGetsDetermined();
+void EnsureSystemVideoCapturePermissionIsOrGetsDetermined();
 
 #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
index a6942b9..a152da6 100644
--- a/chrome/browser/media/webrtc/system_media_capture_permissions_mac.mm
+++ b/chrome/browser/media/webrtc/system_media_capture_permissions_mac.mm
@@ -4,7 +4,8 @@
 //
 // Authorization functions and types are available on 10.14+.
 // To avoid availability compile errors, use performSelector invocation of
-// functions and NSInteger instead of AVAuthorizationStatus.
+// functions, NSInteger instead of AVAuthorizationStatus, and NSString* instead
+// of AVMediaType.
 // The AVAuthorizationStatus enum is defined as follows (10.14 SDK):
 // AVAuthorizationStatusNotDetermined = 0,
 // AVAuthorizationStatusRestricted    = 1,
@@ -21,14 +22,14 @@
 
 namespace {
 
-NSInteger AudioAuthorizationStatus() {
+NSInteger MediaAuthorizationStatus(NSString* media_type) {
   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];
+      auth_status =
+          (NSInteger)[target performSelector:selector withObject:media_type];
     } else {
       DLOG(WARNING) << "authorizationStatusForMediaType could not be executed";
     }
@@ -39,24 +40,23 @@
   return 0;
 }
 
-}  // namespace
-
-bool SystemAudioCapturePermissionIsDisallowed() {
+bool SystemMediaCapturePermissionIsDisallowed(NSString* media_type) {
   if (@available(macOS 10.14, *)) {
-    NSInteger auth_status = AudioAuthorizationStatus();
+    NSInteger auth_status = MediaAuthorizationStatus(media_type);
     return auth_status == 1 || auth_status == 2;
   }
   return false;
 }
 
-void EnsureSystemAudioCapturePermission() {
+void EnsureSystemMediaCapturePermissionIsOrGetsDetermined(
+    NSString* media_type) {
   if (@available(macOS 10.14, *)) {
-    if (AudioAuthorizationStatus() == 0) {
+    if (MediaAuthorizationStatus(media_type) == 0) {
       AVCaptureDevice* target = [AVCaptureDevice class];
       SEL selector = @selector(requestAccessForMediaType:completionHandler:);
       if ([target respondsToSelector:selector]) {
         [target performSelector:selector
-                     withObject:AVMediaTypeAudio
+                     withObject:media_type
                      withObject:^(BOOL granted){
                      }];
       } else {
@@ -65,3 +65,21 @@
     }
   }
 }
+
+}  // namespace
+
+bool SystemAudioCapturePermissionIsDisallowed() {
+  return SystemMediaCapturePermissionIsDisallowed(AVMediaTypeAudio);
+}
+
+bool SystemVideoCapturePermissionIsDisallowed() {
+  return SystemMediaCapturePermissionIsDisallowed(AVMediaTypeVideo);
+}
+
+void EnsureSystemAudioCapturePermissionIsOrGetsDetermined() {
+  EnsureSystemMediaCapturePermissionIsOrGetsDetermined(AVMediaTypeAudio);
+}
+
+void EnsureSystemVideoCapturePermissionIsOrGetsDetermined() {
+  EnsureSystemMediaCapturePermissionIsOrGetsDetermined(AVMediaTypeVideo);
+}