| // 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, NSInteger instead of AVAuthorizationStatus, and NSString* instead |
| // of AVMediaType. |
| // 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/bind_helpers.h" |
| #include "base/callback.h" |
| #include "base/callback_helpers.h" |
| #include "base/command_line.h" |
| #include "base/feature_list.h" |
| #include "base/logging.h" |
| #include "base/mac/foundation_util.h" |
| #include "base/mac/scoped_cftyperef.h" |
| #include "base/macros.h" |
| #include "base/no_destructor.h" |
| #include "base/task/post_task.h" |
| #include "base/task/task_traits.h" |
| #include "chrome/browser/media/webrtc/media_authorization_wrapper_mac.h" |
| #include "chrome/common/chrome_features.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "media/base/media_switches.h" |
| |
| namespace system_media_permissions { |
| |
| namespace { |
| |
| bool UsingFakeMediaDevices() { |
| return base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kUseFakeDeviceForMediaStream); |
| } |
| |
| // Pointer to OS call wrapper that tests can set. |
| MediaAuthorizationWrapper* g_media_authorization_wrapper_for_tests = nullptr; |
| |
| // Implementation of OS call wrapper that does the actual OS calls. |
| class MediaAuthorizationWrapperImpl : public MediaAuthorizationWrapper { |
| public: |
| MediaAuthorizationWrapperImpl() = default; |
| ~MediaAuthorizationWrapperImpl() final = default; |
| |
| NSInteger AuthorizationStatusForMediaType(NSString* media_type) final { |
| 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:media_type]; |
| } else { |
| DLOG(WARNING) |
| << "authorizationStatusForMediaType could not be executed"; |
| } |
| return auth_status; |
| } |
| |
| NOTREACHED(); |
| return 0; |
| } |
| |
| void RequestAccessForMediaType(NSString* media_type, |
| base::RepeatingClosure callback, |
| const base::TaskTraits& traits) final { |
| if (@available(macOS 10.14, *)) { |
| AVCaptureDevice* target = [AVCaptureDevice class]; |
| SEL selector = @selector(requestAccessForMediaType:completionHandler:); |
| if ([target respondsToSelector:selector]) { |
| [target performSelector:selector |
| withObject:media_type |
| withObject:^(BOOL granted) { |
| base::PostTask(FROM_HERE, traits, std::move(callback)); |
| }]; |
| } else { |
| DLOG(WARNING) << "requestAccessForMediaType could not be executed"; |
| base::PostTask(FROM_HERE, traits, std::move(callback)); |
| } |
| } else { |
| NOTREACHED(); |
| base::PostTask(FROM_HERE, traits, std::move(callback)); |
| } |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MediaAuthorizationWrapperImpl); |
| }; |
| |
| MediaAuthorizationWrapper& GetMediaAuthorizationWrapper() { |
| if (g_media_authorization_wrapper_for_tests) |
| return *g_media_authorization_wrapper_for_tests; |
| |
| static base::NoDestructor<MediaAuthorizationWrapperImpl> |
| media_authorization_wrapper; |
| return *media_authorization_wrapper; |
| } |
| |
| NSInteger MediaAuthorizationStatus(NSString* media_type) { |
| if (@available(macOS 10.14, *)) { |
| return GetMediaAuthorizationWrapper().AuthorizationStatusForMediaType( |
| media_type); |
| } |
| |
| NOTREACHED(); |
| return 0; |
| } |
| |
| SystemPermission CheckSystemMediaCapturePermission(NSString* media_type) { |
| if (UsingFakeMediaDevices()) |
| return SystemPermission::kAllowed; |
| |
| if (@available(macOS 10.14, *)) { |
| NSInteger auth_status = MediaAuthorizationStatus(media_type); |
| switch (auth_status) { |
| case 0: |
| return SystemPermission::kNotDetermined; |
| case 1: |
| return SystemPermission::kRestricted; |
| case 2: |
| return SystemPermission::kDenied; |
| case 3: |
| return SystemPermission::kAllowed; |
| default: |
| NOTREACHED(); |
| return SystemPermission::kAllowed; |
| } |
| } |
| |
| // On pre-10.14, there are no system permissions, so we return allowed. |
| return SystemPermission::kAllowed; |
| } |
| |
| // Use RepeatingCallback since it must be copyable for use in the block. It's |
| // only called once though. |
| void RequestSystemMediaCapturePermission(NSString* media_type, |
| base::RepeatingClosure callback, |
| const base::TaskTraits& traits) { |
| if (UsingFakeMediaDevices()) { |
| base::PostTask(FROM_HERE, traits, std::move(callback)); |
| return; |
| } |
| |
| if (@available(macOS 10.14, *)) { |
| GetMediaAuthorizationWrapper().RequestAccessForMediaType( |
| media_type, std::move(callback), traits); |
| } else { |
| NOTREACHED(); |
| // Should never happen since for pre-10.14 system permissions don't exist |
| // and checking them in CheckSystemAudioCapturePermission() will always |
| // return allowed, and this function should not be called. |
| base::PostTask(FROM_HERE, traits, std::move(callback)); |
| } |
| } |
| |
| // Heuristic to check screen capture permission on macOS 10.15. |
| // See https://crbug.com/993692#c3. |
| bool IsScreenCaptureAllowed() { |
| if (@available(macOS 10.15, *)) { |
| if (!base::FeatureList::IsEnabled( |
| features::kMacSystemScreenCapturePermissionCheck)) { |
| return true; |
| } |
| base::ScopedCFTypeRef<CFArrayRef> window_list(CGWindowListCopyWindowInfo( |
| kCGWindowListOptionOnScreenOnly, kCGNullWindowID)); |
| NSUInteger num_windows = CFArrayGetCount(window_list); |
| NSUInteger num_windows_with_name = 0; |
| for (NSDictionary* dict in base::mac::CFToNSCast(window_list.get())) { |
| if ([dict objectForKey:base::mac::CFToNSCast(kCGWindowName)]) { |
| num_windows_with_name++; |
| } else { |
| // No kCGWindowName detected implies no permission. |
| break; |
| } |
| } |
| return num_windows == num_windows_with_name; |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| SystemPermission CheckSystemAudioCapturePermission() { |
| return CheckSystemMediaCapturePermission(AVMediaTypeAudio); |
| } |
| |
| SystemPermission CheckSystemVideoCapturePermission() { |
| return CheckSystemMediaCapturePermission(AVMediaTypeVideo); |
| } |
| |
| SystemPermission CheckSystemScreenCapturePermission() { |
| return IsScreenCaptureAllowed() ? SystemPermission::kAllowed |
| : SystemPermission::kDenied; |
| } |
| |
| void RequestSystemAudioCapturePermisson(base::OnceClosure callback, |
| const base::TaskTraits& traits) { |
| RequestSystemMediaCapturePermission( |
| AVMediaTypeAudio, base::AdaptCallbackForRepeating(std::move(callback)), |
| traits); |
| } |
| |
| void RequestSystemVideoCapturePermisson(base::OnceClosure callback, |
| const base::TaskTraits& traits) { |
| RequestSystemMediaCapturePermission( |
| AVMediaTypeVideo, base::AdaptCallbackForRepeating(std::move(callback)), |
| traits); |
| } |
| |
| void SetMediaAuthorizationWrapperForTesting( |
| MediaAuthorizationWrapper* wrapper) { |
| CHECK(!g_media_authorization_wrapper_for_tests); |
| g_media_authorization_wrapper_for_tests = wrapper; |
| } |
| |
| } // namespace system_media_permissions |