1 // Copyright 2020 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/camera_pan_tilt_zoom_permission_context.h"
6 
7 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
8 #include "chrome/browser/permissions/permission_manager_factory.h"
9 #include "chrome/browser/profiles/profile.h"
10 #include "components/permissions/permission_manager.h"
11 #include "components/permissions/permission_request_id.h"
12 #include "components/permissions/permissions_client.h"
13 #include "content/public/browser/browser_thread.h"
14 #include "third_party/blink/public/mojom/feature_policy/feature_policy_feature.mojom-shared.h"
15 
CameraPanTiltZoomPermissionContext(content::BrowserContext * browser_context)16 CameraPanTiltZoomPermissionContext::CameraPanTiltZoomPermissionContext(
17     content::BrowserContext* browser_context)
18     : PermissionContextBase(browser_context,
19                             ContentSettingsType::CAMERA_PAN_TILT_ZOOM,
20                             blink::mojom::FeaturePolicyFeature::kNotFound) {
21   host_content_settings_map_ =
22       permissions::PermissionsClient::Get()->GetSettingsMap(browser_context);
23   host_content_settings_map_->AddObserver(this);
24 }
25 
~CameraPanTiltZoomPermissionContext()26 CameraPanTiltZoomPermissionContext::~CameraPanTiltZoomPermissionContext() {
27   host_content_settings_map_->RemoveObserver(this);
28 }
29 
RequestPermission(content::WebContents * web_contents,const permissions::PermissionRequestID & id,const GURL & requesting_frame_origin,bool user_gesture,permissions::BrowserPermissionCallback callback)30 void CameraPanTiltZoomPermissionContext::RequestPermission(
31     content::WebContents* web_contents,
32     const permissions::PermissionRequestID& id,
33     const GURL& requesting_frame_origin,
34     bool user_gesture,
35     permissions::BrowserPermissionCallback callback) {
36   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
37 
38   if (HasAvailableCameraPtzDevices()) {
39     PermissionContextBase::RequestPermission(web_contents, id,
40                                              requesting_frame_origin,
41                                              user_gesture, std::move(callback));
42     return;
43   }
44 
45   // If there is no camera with PTZ capabilities, let's request a "regular"
46   // camera permission instead.
47   content::RenderFrameHost* frame = content::RenderFrameHost::FromID(
48       id.render_process_id(), id.render_frame_id());
49   permissions::PermissionManager* permission_manager =
50       PermissionManagerFactory::GetForProfile(
51           Profile::FromBrowserContext(web_contents->GetBrowserContext()));
52   permission_manager->RequestPermission(ContentSettingsType::MEDIASTREAM_CAMERA,
53                                         frame, requesting_frame_origin,
54                                         user_gesture, std::move(callback));
55 }
56 
57 #if defined(OS_ANDROID)
GetPermissionStatusInternal(content::RenderFrameHost * render_frame_host,const GURL & requesting_origin,const GURL & embedding_origin) const58 ContentSetting CameraPanTiltZoomPermissionContext::GetPermissionStatusInternal(
59     content::RenderFrameHost* render_frame_host,
60     const GURL& requesting_origin,
61     const GURL& embedding_origin) const {
62   // The PTZ permission is automatically granted on Android. It is safe to do so
63   // because pan and tilt are not supported on Android.
64   return CONTENT_SETTING_ALLOW;
65 }
66 #endif
67 
IsRestrictedToSecureOrigins() const68 bool CameraPanTiltZoomPermissionContext::IsRestrictedToSecureOrigins() const {
69   return true;
70 }
71 
OnContentSettingChanged(const ContentSettingsPattern & primary_pattern,const ContentSettingsPattern & secondary_pattern,ContentSettingsType content_type)72 void CameraPanTiltZoomPermissionContext::OnContentSettingChanged(
73     const ContentSettingsPattern& primary_pattern,
74     const ContentSettingsPattern& secondary_pattern,
75     ContentSettingsType content_type) {
76   if (content_type != ContentSettingsType::MEDIASTREAM_CAMERA &&
77       content_type != ContentSettingsType::CAMERA_PAN_TILT_ZOOM) {
78     return;
79   }
80 
81   // Skip if the camera permission is currently being updated to match camera
82   // PTZ permission as OnContentSettingChanged would have been called again
83   // causing a reentrancy issue.
84   if (updating_mediastream_camera_permission_) {
85     updating_mediastream_camera_permission_ = false;
86     return;
87   }
88 
89   // Skip if the camera PTZ permission is currently being reset when camera
90   // permission got blocked or reset as OnContentSettingChanged would have been
91   // called again causing a reentrancy issue.
92   if (updating_camera_ptz_permission_) {
93     updating_camera_ptz_permission_ = false;
94     return;
95   }
96 
97   // TODO(crbug.com/1078272): We should not need to deduce the url from the
98   // primary pattern here. Modify the infrastructure to facilitate this
99   // particular use case better.
100   const GURL url(primary_pattern.ToString());
101   if (url::Origin::Create(url).opaque())
102     return;
103 
104   ContentSetting camera_ptz_setting =
105       host_content_settings_map_->GetContentSetting(url, url,
106                                                     content_settings_type());
107 
108   if (content_type == ContentSettingsType::CAMERA_PAN_TILT_ZOOM) {
109     // Automatically update camera permission to camera PTZ permission as any
110     // change to camera PTZ should be reflected to camera.
111     updating_mediastream_camera_permission_ = true;
112     host_content_settings_map_->SetContentSettingCustomScope(
113         primary_pattern, secondary_pattern,
114         ContentSettingsType::MEDIASTREAM_CAMERA, camera_ptz_setting);
115     return;
116   }
117 
118   // Don't reset camera PTZ permission if it is already blocked or in a
119   // "default" state.
120   if (camera_ptz_setting == CONTENT_SETTING_BLOCK ||
121       camera_ptz_setting == CONTENT_SETTING_ASK) {
122     return;
123   }
124 
125   ContentSetting mediastream_camera_setting =
126       host_content_settings_map_->GetContentSetting(url, url, content_type);
127   if (mediastream_camera_setting == CONTENT_SETTING_BLOCK ||
128       mediastream_camera_setting == CONTENT_SETTING_ASK) {
129     // Automatically reset camera PTZ permission if camera permission
130     // gets blocked or reset.
131     updating_camera_ptz_permission_ = true;
132     host_content_settings_map_->SetContentSettingCustomScope(
133         primary_pattern, secondary_pattern,
134         ContentSettingsType::CAMERA_PAN_TILT_ZOOM, CONTENT_SETTING_DEFAULT);
135   }
136 }
137 
HasAvailableCameraPtzDevices() const138 bool CameraPanTiltZoomPermissionContext::HasAvailableCameraPtzDevices() const {
139   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
140 
141   const std::vector<blink::MediaStreamDevice> devices =
142       MediaCaptureDevicesDispatcher::GetInstance()->GetVideoCaptureDevices();
143   for (const blink::MediaStreamDevice& device : devices) {
144     if (device.video_control_support.pan || device.video_control_support.tilt ||
145         device.video_control_support.zoom) {
146       return true;
147     }
148   }
149   return false;
150 }
151