1// Copyright 2019 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
6
7#import <AVFoundation/AVFoundation.h>
8
9#include "base/callback.h"
10#include "base/callback_helpers.h"
11#include "base/command_line.h"
12#include "base/feature_list.h"
13#include "base/logging.h"
14#include "base/mac/foundation_util.h"
15#include "base/mac/scoped_cftyperef.h"
16#include "base/macros.h"
17#include "base/no_destructor.h"
18#include "base/task/post_task.h"
19#include "base/task/task_traits.h"
20#include "chrome/browser/media/webrtc/media_authorization_wrapper_mac.h"
21#include "chrome/common/chrome_features.h"
22#include "content/public/browser/browser_task_traits.h"
23#include "content/public/browser/browser_thread.h"
24#include "media/base/media_switches.h"
25#include "ui/base/cocoa/permissions_utils.h"
26
27namespace system_media_permissions {
28
29namespace {
30
31bool UsingFakeMediaDevices() {
32  return base::CommandLine::ForCurrentProcess()->HasSwitch(
33      switches::kUseFakeDeviceForMediaStream);
34}
35
36// Pointer to OS call wrapper that tests can set.
37MediaAuthorizationWrapper* g_media_authorization_wrapper_for_tests = nullptr;
38
39// Implementation of OS call wrapper that does the actual OS calls.
40class MediaAuthorizationWrapperImpl : public MediaAuthorizationWrapper {
41 public:
42  MediaAuthorizationWrapperImpl() = default;
43  ~MediaAuthorizationWrapperImpl() final = default;
44
45  NSInteger AuthorizationStatusForMediaType(AVMediaType media_type) final {
46    if (@available(macOS 10.14, *)) {
47      return [AVCaptureDevice authorizationStatusForMediaType:media_type];
48    } else {
49      NOTREACHED();
50      return 0;
51    }
52  }
53
54  void RequestAccessForMediaType(AVMediaType media_type,
55                                 base::RepeatingClosure callback,
56                                 const base::TaskTraits& traits) final {
57    if (@available(macOS 10.14, *)) {
58      [AVCaptureDevice
59          requestAccessForMediaType:media_type
60                  completionHandler:^(BOOL granted) {
61                    base::PostTask(FROM_HERE, traits, std::move(callback));
62                  }];
63    } else {
64      NOTREACHED();
65      base::PostTask(FROM_HERE, traits, std::move(callback));
66    }
67  }
68
69 private:
70  DISALLOW_COPY_AND_ASSIGN(MediaAuthorizationWrapperImpl);
71};
72
73MediaAuthorizationWrapper& GetMediaAuthorizationWrapper() {
74  if (g_media_authorization_wrapper_for_tests)
75    return *g_media_authorization_wrapper_for_tests;
76
77  static base::NoDestructor<MediaAuthorizationWrapperImpl>
78      media_authorization_wrapper;
79  return *media_authorization_wrapper;
80}
81
82NSInteger MediaAuthorizationStatus(AVMediaType media_type) {
83  if (@available(macOS 10.14, *)) {
84    return GetMediaAuthorizationWrapper().AuthorizationStatusForMediaType(
85        media_type);
86  }
87
88  NOTREACHED();
89  return 0;
90}
91
92SystemPermission CheckSystemMediaCapturePermission(AVMediaType media_type) {
93  if (UsingFakeMediaDevices())
94    return SystemPermission::kAllowed;
95
96  if (@available(macOS 10.14, *)) {
97    NSInteger auth_status = MediaAuthorizationStatus(media_type);
98    switch (auth_status) {
99      case AVAuthorizationStatusNotDetermined:
100        return SystemPermission::kNotDetermined;
101      case AVAuthorizationStatusRestricted:
102        return SystemPermission::kRestricted;
103      case AVAuthorizationStatusDenied:
104        return SystemPermission::kDenied;
105      case AVAuthorizationStatusAuthorized:
106        return SystemPermission::kAllowed;
107      default:
108        NOTREACHED();
109        return SystemPermission::kAllowed;
110    }
111  }
112
113  // On pre-10.14, there are no system permissions, so we return allowed.
114  return SystemPermission::kAllowed;
115}
116
117// Use RepeatingCallback since it must be copyable for use in the block. It's
118// only called once though.
119void RequestSystemMediaCapturePermission(AVMediaType media_type,
120                                         base::RepeatingClosure callback,
121                                         const base::TaskTraits& traits) {
122  if (UsingFakeMediaDevices()) {
123    base::PostTask(FROM_HERE, traits, std::move(callback));
124    return;
125  }
126
127  if (@available(macOS 10.14, *)) {
128    GetMediaAuthorizationWrapper().RequestAccessForMediaType(
129        media_type, std::move(callback), traits);
130  } else {
131    NOTREACHED();
132    // Should never happen since for pre-10.14 system permissions don't exist
133    // and checking them in CheckSystemAudioCapturePermission() will always
134    // return allowed, and this function should not be called.
135    base::PostTask(FROM_HERE, traits, std::move(callback));
136  }
137}
138
139// Heuristic to check screen capture permission on macOS 10.15.
140// Screen Capture is considered allowed if the name of at least one normal
141// or dock window running on another process is visible.
142// See https://crbug.com/993692.
143bool IsScreenCaptureAllowed() {
144  if (@available(macOS 10.15, *)) {
145    if (!base::FeatureList::IsEnabled(
146            features::kMacSystemScreenCapturePermissionCheck)) {
147      return true;
148    }
149  }
150
151  return ui::IsScreenCaptureAllowed();
152}
153
154}  // namespace
155
156SystemPermission CheckSystemAudioCapturePermission() {
157  return CheckSystemMediaCapturePermission(AVMediaTypeAudio);
158}
159
160SystemPermission CheckSystemVideoCapturePermission() {
161  return CheckSystemMediaCapturePermission(AVMediaTypeVideo);
162}
163
164SystemPermission CheckSystemScreenCapturePermission() {
165  return IsScreenCaptureAllowed() ? SystemPermission::kAllowed
166                                  : SystemPermission::kDenied;
167}
168
169void RequestSystemAudioCapturePermisson(base::OnceClosure callback,
170                                        const base::TaskTraits& traits) {
171  RequestSystemMediaCapturePermission(
172      AVMediaTypeAudio, base::AdaptCallbackForRepeating(std::move(callback)),
173      traits);
174}
175
176void RequestSystemVideoCapturePermisson(base::OnceClosure callback,
177                                        const base::TaskTraits& traits) {
178  RequestSystemMediaCapturePermission(
179      AVMediaTypeVideo, base::AdaptCallbackForRepeating(std::move(callback)),
180      traits);
181}
182
183void SetMediaAuthorizationWrapperForTesting(
184    MediaAuthorizationWrapper* wrapper) {
185  CHECK(!g_media_authorization_wrapper_for_tests);
186  g_media_authorization_wrapper_for_tests = wrapper;
187}
188
189}  // namespace system_media_permissions
190