// 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.

#include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"

#import <AVFoundation/AVFoundation.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"
#include "ui/base/cocoa/permissions_utils.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(AVMediaType media_type) final {
    if (@available(macOS 10.14, *)) {
      return [AVCaptureDevice authorizationStatusForMediaType:media_type];
    } else {
      NOTREACHED();
      return 0;
    }
  }

  void RequestAccessForMediaType(AVMediaType media_type,
                                 base::RepeatingClosure callback,
                                 const base::TaskTraits& traits) final {
    if (@available(macOS 10.14, *)) {
      [AVCaptureDevice
          requestAccessForMediaType:media_type
                  completionHandler:^(BOOL granted) {
                    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(AVMediaType media_type) {
  if (@available(macOS 10.14, *)) {
    return GetMediaAuthorizationWrapper().AuthorizationStatusForMediaType(
        media_type);
  }

  NOTREACHED();
  return 0;
}

SystemPermission CheckSystemMediaCapturePermission(AVMediaType media_type) {
  if (UsingFakeMediaDevices())
    return SystemPermission::kAllowed;

  if (@available(macOS 10.14, *)) {
    NSInteger auth_status = MediaAuthorizationStatus(media_type);
    switch (auth_status) {
      case AVAuthorizationStatusNotDetermined:
        return SystemPermission::kNotDetermined;
      case AVAuthorizationStatusRestricted:
        return SystemPermission::kRestricted;
      case AVAuthorizationStatusDenied:
        return SystemPermission::kDenied;
      case AVAuthorizationStatusAuthorized:
        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(AVMediaType 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.
// Screen Capture is considered allowed if the name of at least one normal
// or dock window running on another process is visible.
// See https://crbug.com/993692.
bool IsScreenCaptureAllowed() {
  if (@available(macOS 10.15, *)) {
    if (!base::FeatureList::IsEnabled(
            features::kMacSystemScreenCapturePermissionCheck)) {
      return true;
    }
  }

  return ui::IsScreenCaptureAllowed();
}

}  // 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
