Commit 717d7879 authored by Francois Beaufort's avatar Francois Beaufort Committed by Commit Bot

[PTZ] Put zoom behind the permission on Desktop

This CL makes sure zoom is put behind the panTiltZoom permission like
pan and tilt. The only exception is on Android where zoom is granted
automatically.

This CL also adds proper PTZ camera support on Android so that web
developers who specifically ask for zoom on an Android camera that does
not support it (e.g. infrared) get an OverConstrainedError.

Change-Id: I80a37e75d6eec7ab134b9296c222a6d450f26078
Bug: 1104080
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2279898Reviewed-by: default avatarReilly Grant <reillyg@chromium.org>
Reviewed-by: default avatarAndy Paicu <andypaicu@chromium.org>
Reviewed-by: default avatarBalazs Engedy <engedy@chromium.org>
Reviewed-by: default avatarGuido Urdaneta <guidou@chromium.org>
Commit-Queue: François Beaufort <beaufort.francois@gmail.com>
Cr-Commit-Position: refs/heads/master@{#789756}
parent 343c0868
......@@ -116,6 +116,15 @@ public class PageInfoViewTest {
});
}
private void addDefaultSettingPermissions(String url) {
TestThreadUtils.runOnUiThreadBlocking(() -> {
WebsitePreferenceBridge.setContentSettingForPattern(Profile.getLastUsedRegularProfile(),
ContentSettingsType.MEDIASTREAM_MIC, url, "*", ContentSettingValues.DEFAULT);
WebsitePreferenceBridge.setContentSettingForPattern(Profile.getLastUsedRegularProfile(),
ContentSettingsType.MEDIASTREAM_CAMERA, url, "*", ContentSettingValues.ASK);
});
}
@Before
public void setUp() throws InterruptedException {
// Some test devices have geolocation disabled. Override LocationUtils for a stable result.
......@@ -226,6 +235,18 @@ public class PageInfoViewTest {
mRenderTestRule.render(getPageInfoView(), "PageInfo_PermissionsAndCookieBlocking");
}
/**
* Tests PageInfo on a website with default setting permissions.
*/
@Test
@MediumTest
@Feature({"RenderTest"})
public void testShowWithDefaultSettingPermissions() throws IOException {
addDefaultSettingPermissions(mTestServerRule.getServer().getURL("/"));
loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(mPath));
mRenderTestRule.render(getPageInfoView(), "PageInfo_DefaultSettingPermissions");
}
/**
* Tests the new PageInfo UI on a secure website.
*/
......
......@@ -54,6 +54,17 @@ void CameraPanTiltZoomPermissionContext::RequestPermission(
user_gesture, std::move(callback));
}
#if defined(OS_ANDROID)
ContentSetting CameraPanTiltZoomPermissionContext::GetPermissionStatusInternal(
content::RenderFrameHost* render_frame_host,
const GURL& requesting_origin,
const GURL& embedding_origin) const {
// The PTZ permission is automatically granted on Android. It is safe to do so
// because pan and tilt are not supported on Android.
return CONTENT_SETTING_ALLOW;
}
#endif
bool CameraPanTiltZoomPermissionContext::IsRestrictedToSecureOrigins() const {
return true;
}
......
......@@ -6,6 +6,7 @@
#define CHROME_BROWSER_MEDIA_WEBRTC_CAMERA_PAN_TILT_ZOOM_PERMISSION_CONTEXT_H_
#include "base/macros.h"
#include "build/build_config.h"
#include "components/content_settings/core/browser/content_settings_observer.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/permissions/permission_context_base.h"
......@@ -34,6 +35,12 @@ class CameraPanTiltZoomPermissionContext
const GURL& requesting_frame_origin,
bool user_gesture,
permissions::BrowserPermissionCallback callback) override;
#if defined(OS_ANDROID)
ContentSetting GetPermissionStatusInternal(
content::RenderFrameHost* render_frame_host,
const GURL& requesting_origin,
const GURL& embedding_origin) const override;
#endif
bool IsRestrictedToSecureOrigins() const override;
// content_settings::Observer
......
......@@ -173,8 +173,10 @@ void PageInfoControllerAndroid::SetPermissionInfo(
base::Optional<ContentSetting> PageInfoControllerAndroid::GetSettingToDisplay(
const PermissionInfo& permission) {
// All permissions should be displayed if they are non-default.
if (permission.setting != CONTENT_SETTING_DEFAULT)
if (permission.setting != CONTENT_SETTING_DEFAULT &&
permission.setting != permission.default_setting) {
return permission.setting;
}
// Handle exceptions for permissions which need to be displayed even if they
// are set to the default.
......
......@@ -114,10 +114,9 @@ void ImageCaptureImpl::SetOptions(const std::string& source_id,
"ImageCaptureImpl::SetOptions",
TRACE_EVENT_SCOPE_PROCESS);
// TODO(crbug.com/934063): Check "has_zoom" as well if upcoming metrics show
// that zoom may be moved under this permission.
if ((settings->has_pan || settings->has_tilt) &&
!HasPanTiltZoomPermissionGranted()) {
if (((settings->has_pan || settings->has_tilt) &&
!HasPanTiltZoomPermissionGranted()) ||
(settings->has_zoom && !HasZoomPermissionGranted())) {
std::move(callback).Run(false);
return;
}
......@@ -160,13 +159,13 @@ ImageCaptureImpl::~ImageCaptureImpl() = default;
void ImageCaptureImpl::OnGetPhotoState(GetPhotoStateCallback callback,
media::mojom::PhotoStatePtr state) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// TODO(crbug.com/934063): Reset "zoom" as well if upcoming metrics show
// that zoom may be moved under this permission.
if (!HasPanTiltZoomPermissionGranted()) {
state->pan = media::mojom::Range::New();
state->tilt = media::mojom::Range::New();
}
if (!HasZoomPermissionGranted()) {
state->zoom = media::mojom::Range::New();
}
std::move(callback).Run(std::move(state));
}
......@@ -178,4 +177,12 @@ bool ImageCaptureImpl::HasPanTiltZoomPermissionGranted() {
render_frame_host()->GetProcess()->GetID(),
render_frame_host()->GetRoutingID());
}
bool ImageCaptureImpl::HasZoomPermissionGranted() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
return MediaDevicesPermissionChecker::HasZoomPermissionGrantedOnUIThread(
render_frame_host()->GetProcess()->GetID(),
render_frame_host()->GetRoutingID());
}
} // namespace content
......@@ -38,6 +38,7 @@ class ImageCaptureImpl final
media::mojom::PhotoStatePtr);
bool HasPanTiltZoomPermissionGranted();
bool HasZoomPermissionGranted();
base::WeakPtrFactory<ImageCaptureImpl> weak_factory_{this};
......
......@@ -9,6 +9,7 @@
#include "base/bind.h"
#include "base/command_line.h"
#include "build/build_config.h"
#include "content/browser/frame_host/render_frame_host_delegate.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/public/browser/browser_context.h"
......@@ -150,12 +151,41 @@ bool MediaDevicesPermissionChecker::HasPanTiltZoomPermissionGrantedOnUIThread(
int render_process_id,
int render_frame_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
#if defined(OS_ANDROID)
// The PTZ permission is automatically granted on Android, regardless of the
// MediaCapturePanTilt Blink feature state. This way, zoom is not initially
// empty in ImageCapture. It is safe to do so because pan and tilt are not
// supported on Android.
return true;
#else
// TODO(crbug.com/934063): Remove when MediaCapturePanTilt Blink feature is
// enabled by default.
if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableExperimentalWebPlatformFeatures)) {
return false;
}
return IsPanTiltZoomAllowedOnUIThread(render_process_id, render_frame_id);
#endif
}
// static
// TODO(crbug.com/934063): Remove when MediaCapturePanTilt Blink feature is
// enabled by default.
bool MediaDevicesPermissionChecker::HasZoomPermissionGrantedOnUIThread(
int render_process_id,
int render_frame_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
#if defined(OS_ANDROID)
return true;
#else
return IsPanTiltZoomAllowedOnUIThread(render_process_id, render_frame_id);
#endif
}
bool MediaDevicesPermissionChecker::IsPanTiltZoomAllowedOnUIThread(
int render_process_id,
int render_frame_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
RenderFrameHostImpl* frame_host =
RenderFrameHostImpl::FromID(render_process_id, render_frame_id);
......
......@@ -62,11 +62,22 @@ class CONTENT_EXPORT MediaDevicesPermissionChecker {
// movement (pan, tilt, and zoom). Otherwise, returns false.
static bool HasPanTiltZoomPermissionGrantedOnUIThread(int render_process_id,
int render_frame_id);
// Returns true if the origin associated to a render frame identified by
// |render_process_id| and |render_frame_id| is allowed to control camera
// zoom. Otherwise, returns false.
static bool HasZoomPermissionGrantedOnUIThread(int render_process_id,
int render_frame_id);
private:
const bool use_override_;
const bool override_value_;
// Returns true if the origin associated to a render frame identified by
// |render_process_id| and |render_frame_id| is allowed to control camera
// movement (pan, tilt, and zoom). Otherwise, returns false.
static bool IsPanTiltZoomAllowedOnUIThread(int render_process_id,
int render_frame_id);
DISALLOW_COPY_AND_ASSIGN(MediaDevicesPermissionChecker);
};
......
......@@ -189,6 +189,24 @@ public class VideoCaptureCamera
return VideoCaptureApi.ANDROID_API1;
}
static boolean isPanTiltZoomSupported(int id) {
android.hardware.Camera camera;
try {
camera = android.hardware.Camera.open(id);
} catch (RuntimeException ex) {
Log.e(TAG, "Camera.open: ", ex);
return false;
}
android.hardware.Camera.Parameters parameters = getCameraParameters(camera);
if (parameters == null) {
return false;
}
final boolean isZoomSupported = parameters.isZoomSupported();
camera.release();
return isZoomSupported;
}
static int getFacingMode(int id) {
android.hardware.Camera.CameraInfo cameraInfo = VideoCaptureCamera.getCameraInfo(id);
if (cameraInfo == null) {
......
......@@ -1363,6 +1363,18 @@ public class VideoCaptureCamera2 extends VideoCapture {
}
}
public static boolean isPanTiltZoomSupported(int id) {
final CameraCharacteristics cameraCharacteristics = getCameraCharacteristics(id);
if (cameraCharacteristics == null) {
return false;
}
final float maxZoom =
cameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
final boolean isZoomSupported = maxZoom > 1.0f;
return isZoomSupported;
}
public static int getFacingMode(int id) {
final CameraCharacteristics cameraCharacteristics = getCameraCharacteristics(id);
if (cameraCharacteristics == null) {
......
......@@ -80,6 +80,14 @@ class VideoCaptureFactory {
return VideoCaptureCamera2.getCaptureApiType(id);
}
@CalledByNative
static boolean isPanTiltZoomSupported(int id) {
if (isLegacyOrDeprecatedDevice(id)) {
return VideoCaptureCamera.isPanTiltZoomSupported(id);
}
return VideoCaptureCamera2.isPanTiltZoomSupported(id);
}
@CalledByNative
static int getFacingMode(int id) {
if (isLegacyOrDeprecatedDevice(id)) {
......
......@@ -68,21 +68,22 @@ void VideoCaptureDeviceFactoryAndroid::GetDeviceDescriptors(
if (device_name.obj() == NULL)
continue;
const std::string display_name =
base::android::ConvertJavaStringToUTF8(device_name);
const std::string device_id = base::NumberToString(camera_id);
const int capture_api_type =
Java_VideoCaptureFactory_getCaptureApiType(env, camera_id);
bool pan_tilt_zoom_supported =
Java_VideoCaptureFactory_isPanTiltZoomSupported(env, camera_id);
const int facing_mode =
Java_VideoCaptureFactory_getFacingMode(env, camera_id);
const std::string display_name =
base::android::ConvertJavaStringToUTF8(device_name);
const std::string device_id = base::NumberToString(camera_id);
// Android cameras are not typically USB devices, and the model_id is
// currently only used for USB model identifiers, so this implementation
// just indicates an unknown device model (by not providing one).
VideoCaptureDeviceDescriptor descriptor(
display_name, device_id, "" /*model_id*/,
static_cast<VideoCaptureApi>(capture_api_type),
/*pan_tilt_zoom_supported=*/false,
static_cast<VideoCaptureApi>(capture_api_type), pan_tilt_zoom_supported,
VideoCaptureTransportType::OTHER_TRANSPORT,
static_cast<VideoFacingMode>(facing_mode));
......
......@@ -351,10 +351,9 @@ void ImageCapture::GetMediaTrackCapabilities(
if (capabilities_->hasTilt())
capabilities->setTilt(capabilities_->tilt());
}
// TODO(crbug.com/934063): Check HasPanTiltZoomPermissionGranted() as well if
// upcoming metrics show that zoom may be moved under this permission.
if (capabilities_->hasZoom())
if (capabilities_->hasZoom() && HasZoomPermissionGranted()) {
capabilities->setZoom(capabilities_->zoom());
}
if (capabilities_->hasTorch())
capabilities->setTorch(capabilities_->torch());
......@@ -430,9 +429,8 @@ void ImageCapture::SetMediaTrackConstraints(
!(capabilities_->hasPan() && HasPanTiltZoomPermissionGranted())) ||
(constraints->hasTilt() &&
!(capabilities_->hasTilt() && HasPanTiltZoomPermissionGranted())) ||
// TODO(crbug.com/934063): Check HasPanTiltZoomPermissionGranted() as well
// if upcoming metrics show that zoom may be moved under this permission.
(constraints->hasZoom() && !capabilities_->hasZoom()) ||
(constraints->hasZoom() &&
!(capabilities_->hasZoom() && HasZoomPermissionGranted())) ||
(constraints->hasTorch() && !capabilities_->hasTorch())) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNotSupportedError, "Unsupported constraint(s)"));
......@@ -818,10 +816,9 @@ void ImageCapture::GetMediaTrackSettings(MediaTrackSettings* settings) const {
if (settings_->hasTilt())
settings->setTilt(settings_->tilt());
}
// TODO(crbug.com/934063): Check HasPanTiltZoomPermissionGranted() as well if
// upcoming metrics show that zoom may be moved under this permission.
if (settings_->hasZoom())
if (settings_->hasZoom() && HasZoomPermissionGranted()) {
settings->setZoom(settings_->zoom());
}
if (settings_->hasTorch())
settings->setTorch(settings_->torch());
......@@ -890,6 +887,10 @@ bool ImageCapture::HasPanTiltZoomPermissionGranted() const {
return pan_tilt_zoom_permission_ == mojom::blink::PermissionStatus::GRANTED;
}
bool ImageCapture::HasZoomPermissionGranted() const {
return pan_tilt_zoom_permission_ == mojom::blink::PermissionStatus::GRANTED;
}
void ImageCapture::OnMojoGetPhotoState(
ScriptPromiseResolver* resolver,
PromiseResolverFunction resolve_function,
......@@ -1098,11 +1099,11 @@ void ImageCapture::UpdateMediaTrackCapabilities(
settings_->setTilt(photo_state->tilt->current);
}
}
// TODO(crbug.com/934063): Check HasPanTiltZoomPermissionGranted() as well if
// upcoming metrics show that zoom may be moved under this permission.
if (photo_state->zoom->max != photo_state->zoom->min) {
capabilities_->setZoom(MediaSettingsRange::Create(*photo_state->zoom));
settings_->setZoom(photo_state->zoom->current);
if (HasZoomPermissionGranted()) {
if (photo_state->zoom->max != photo_state->zoom->min) {
capabilities_->setZoom(MediaSettingsRange::Create(*photo_state->zoom));
settings_->setZoom(photo_state->zoom->current);
}
}
if (photo_state->supports_torch)
......
......@@ -85,6 +85,7 @@ class MODULES_EXPORT ImageCapture final
void GetMediaTrackSettings(MediaTrackSettings*) const;
bool HasPanTiltZoomPermissionGranted() const;
bool HasZoomPermissionGranted() const;
void Trace(Visitor*) const override;
......
......@@ -159,14 +159,16 @@ function makeImageCaptureTest(hasPanTiltZoomPermissionGranted) {
assert_equals(capabilities.tilt.max, mockCapabilities.tilt.max);
assert_equals(capabilities.tilt.min, mockCapabilities.tilt.min);
assert_equals(capabilities.tilt.step, mockCapabilities.tilt.step);
assert_true(capabilities.zoom instanceof MediaSettingsRange);
assert_equals(capabilities.zoom.max, mockCapabilities.zoom.max);
assert_equals(capabilities.zoom.min, mockCapabilities.zoom.min);
assert_equals(capabilities.zoom.step, mockCapabilities.zoom.step);
} else if (ptzPermission === 'denied') {
assert_false('pan' in capabilities);
assert_false('tilt' in capabilities);
assert_false('zoom' in capabilities);
}
assert_true(capabilities.zoom instanceof MediaSettingsRange);
assert_equals(capabilities.zoom.max, mockCapabilities.zoom.max);
assert_equals(capabilities.zoom.min, mockCapabilities.zoom.min);
assert_equals(capabilities.zoom.step, mockCapabilities.zoom.step);
assert_equals(capabilities.torch, mockCapabilities.supportsTorch,
'torch');
......
......@@ -73,11 +73,12 @@ function makeImageCaptureTest(hasPanTiltZoomPermissionGranted) {
if (ptzPermission === 'granted') {
assert_equals(settings.pan, mockSettings.pan.current);
assert_equals(settings.tilt, mockSettings.tilt.current);
assert_equals(settings.zoom, mockSettings.zoom.current);
} else if (ptzPermission === 'denied') {
assert_false('pan' in settings);
assert_false('tilt' in settings);
assert_false('zoom' in settings);
}
assert_equals(settings.zoom, mockSettings.zoom.current);
assert_equals(settings.torch, mockSettings.torch, 'torch');
});
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment