blob: 178f600395fc49e8b8ce8979d61a0da213cfbec8 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/android/display_android_manager.h"
#include <string>
#include <vector>
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/android/ui_android_features.h"
#include "ui/display/display.h"
#include "ui/display/screen_base.h"
#include "ui/display/util/display_util.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
namespace ui {
namespace {
struct DisplayParams {
int32_t sdk_display_id;
std::string label;
std::vector<int32_t> bounds; // {left, top, right, bottom}
gfx::Rect scaled_bounds;
std::vector<int32_t> insets; // {left, top, right, bottom}
gfx::Rect scaled_work_area;
float dip_scale;
float pixels_per_inch_x;
float pixels_per_inch_y;
int32_t rotation_degrees;
int32_t bits_per_pixel;
int32_t bits_per_component;
bool is_internal;
};
const DisplayParams kPrimaryDisplayParams = {
.sdk_display_id = 0,
.label = "primary",
.bounds = {0, 0, 1920, 1080},
.scaled_bounds = {0, 0, 1536, 864},
.insets = {10, 20, 30, 40},
.scaled_work_area = {8, 16, 1504, 816},
.dip_scale = 1.25,
.pixels_per_inch_x = 160,
.pixels_per_inch_y = 160,
.rotation_degrees = 0,
.bits_per_pixel = 24,
.bits_per_component = 8,
.is_internal = true};
const DisplayParams kExternalDisplayParams{
.sdk_display_id = 1,
.label = "external",
.bounds = {0, -1440, 900, 0},
.scaled_bounds = {0, -1440, 900, 1440},
.insets = {1, 2, 3, 4},
.scaled_work_area = {1, -1438, 896, 1434},
.dip_scale = 1,
.pixels_per_inch_x = 160,
.pixels_per_inch_y = 160,
.rotation_degrees = 90,
.bits_per_pixel = 12,
.bits_per_component = 0,
.is_internal = false};
const DisplayParams kSecondExternalDisplayParams{
.sdk_display_id = 2,
.label = "second_external",
.bounds = {-720, 192, 0, 672},
.scaled_bounds = {-720, 192, 720, 480},
.insets = {0, 0, 0, 0},
.scaled_work_area = {-720, 192, 720, 480},
.dip_scale = 1,
.pixels_per_inch_x = 160,
.pixels_per_inch_y = 160,
.rotation_degrees = 0,
.bits_per_pixel = 12,
.bits_per_component = 0,
.is_internal = false};
} // namespace
class DisplayAndroidManagerTest : public testing::Test {
public:
DisplayAndroidManagerTest()
: env_(base::android::AttachCurrentThread()),
display_android_manager_(false) {
display_android_manager_.SetPrimaryDisplayId(
env_, kPrimaryDisplayParams.sdk_display_id);
}
void AddDisplay(const DisplayParams& display_params) {
display_android_manager_.UpdateDisplay(
env_, display_params.sdk_display_id,
base::android::ConvertUTF8ToJavaString(env_, display_params.label),
base::android::ToJavaIntArray(env_, display_params.bounds),
base::android::ToJavaIntArray(env_, display_params.insets),
display_params.dip_scale, display_params.pixels_per_inch_x,
display_params.pixels_per_inch_y, display_params.rotation_degrees,
display_params.bits_per_pixel, display_params.bits_per_component,
/* isWideColorGamut= */ false, /* isHdr= */ false,
/* hdrMaxLuminanceRatio= */ 1.0, display_params.is_internal);
}
void RemoveDisplay(int32_t sdk_display_id) {
display_android_manager_.RemoveDisplay(env_, sdk_display_id);
}
display::Display GetDisplay(int32_t sdk_display_id) const {
display::Display display;
display_android_manager_.GetDisplayWithDisplayId(sdk_display_id, &display);
return display;
}
int GetDisplaysNum() const {
return display_android_manager_.GetNumDisplays();
}
display::Display GetDisplayNearestPoint(const gfx::Point& point) const {
return display_android_manager_.GetDisplayNearestPoint(point);
}
display::Display GetDisplayMatching(const gfx::Rect& match_rect) const {
return display_android_manager_.GetDisplayMatching(match_rect);
}
void CompareDisplayParamsWithDisplay(
const DisplayParams& display_params) const {
const auto& display = GetDisplay(display_params.sdk_display_id);
EXPECT_EQ(display.id(), display_params.sdk_display_id);
EXPECT_EQ(display.label(), display_params.label);
EXPECT_EQ(display.GetSizeInPixel(),
gfx::Size(display_params.bounds[2] - display_params.bounds[0],
display_params.bounds[3] - display_params.bounds[1]));
EXPECT_EQ(display.RotationAsDegree(), display_params.rotation_degrees);
EXPECT_EQ(display.device_scale_factor(), display_params.dip_scale);
EXPECT_EQ(display.bounds(), display_params.scaled_bounds);
if (base::FeatureList::IsEnabled(kAndroidUseCorrectDisplayWorkArea)) {
EXPECT_EQ(display.work_area(), display_params.scaled_work_area);
} else {
EXPECT_EQ(display.work_area(), display_params.scaled_bounds);
}
EXPECT_EQ(display.color_depth(), display_params.bits_per_pixel);
EXPECT_EQ(display.depth_per_component(), display_params.bits_per_component);
EXPECT_EQ(display.is_monochrome(), display_params.bits_per_component == 0);
EXPECT_EQ(display.IsInternal(), display_params.is_internal);
}
private:
raw_ptr<JNIEnv> env_;
DisplayAndroidManager display_android_manager_;
};
TEST_F(DisplayAndroidManagerTest, General) {
// Checking the initial state
EXPECT_EQ(GetDisplaysNum(), 0);
EXPECT_TRUE(display::GetInternalDisplayIds().empty());
// Adding the primary internal display
AddDisplay(kPrimaryDisplayParams);
EXPECT_EQ(GetDisplaysNum(), 1);
EXPECT_EQ(display::GetInternalDisplayIds().size(), std::size_t(1));
// Adding the external display
AddDisplay(kExternalDisplayParams);
EXPECT_EQ(GetDisplaysNum(), 2);
EXPECT_EQ(display::GetInternalDisplayIds().size(), std::size_t(1));
// Comparing displays
CompareDisplayParamsWithDisplay(kPrimaryDisplayParams);
CompareDisplayParamsWithDisplay(kExternalDisplayParams);
// Removing external display
RemoveDisplay(kExternalDisplayParams.sdk_display_id);
EXPECT_EQ(display::GetInternalDisplayIds().size(), std::size_t(1));
// Removing internal display
RemoveDisplay(kPrimaryDisplayParams.sdk_display_id);
// Checking the finished state
EXPECT_EQ(GetDisplaysNum(), 0);
EXPECT_TRUE(display::GetInternalDisplayIds().empty());
}
TEST_F(DisplayAndroidManagerTest, WorkArea) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(kAndroidUseCorrectDisplayWorkArea);
AddDisplay(kPrimaryDisplayParams);
CompareDisplayParamsWithDisplay(kPrimaryDisplayParams);
AddDisplay(kExternalDisplayParams);
CompareDisplayParamsWithDisplay(kExternalDisplayParams);
}
TEST_F(DisplayAndroidManagerTest, DisplayTopology) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(kAndroidWindowManagementWebApi);
// Display Topology
// (0, -1440) +-------------+
// | |
// | |
// | |
// | |
// | |
// | |
// | | (900, 0)
// (0, 0) +-------------+----------+
// | |
// (-720, 192) +---------+ |
// | | |
// +---------+ (0, 672) |
// | |
// +------------------------+ (1536, 864)
AddDisplay(kPrimaryDisplayParams);
AddDisplay(kExternalDisplayParams);
AddDisplay(kSecondExternalDisplayParams);
// Point is located on the border between primary and second external
// displays. This border belongs to the primary display.
EXPECT_EQ(GetDisplayNearestPoint(gfx::Point(0, 432)).id(),
kPrimaryDisplayParams.sdk_display_id);
{
// Point is located on the equal distance from external and second
// external displays. Each of them can be returned.
const auto display_id = GetDisplayNearestPoint(gfx::Point(-384, -192)).id();
EXPECT_TRUE(display_id == kExternalDisplayParams.sdk_display_id ||
display_id == kSecondExternalDisplayParams.sdk_display_id);
}
// Point is located near primary and external displays. If the display is
// determined by pixel coordinates, the primary display will be closer to the
// point.
EXPECT_EQ(GetDisplayNearestPoint(gfx::Point(1800, -700)).id(),
kExternalDisplayParams.sdk_display_id);
// Point is at the corner of the external display.
EXPECT_EQ(GetDisplayNearestPoint(gfx::Point(900, -1440)).id(),
kExternalDisplayParams.sdk_display_id);
// Point doesn't belong to any displays.
EXPECT_EQ(GetDisplayNearestPoint(gfx::Point(-900, -500)).id(),
kSecondExternalDisplayParams.sdk_display_id);
// Rectangle covers all displays. Display with the most area should be
// returned.
EXPECT_EQ(GetDisplayMatching(gfx::Rect(-720, -1440, 2256, 2304)).id(),
kPrimaryDisplayParams.sdk_display_id);
// Rectangle intersects with primary and external displays. The center of the
// rectangle is located on the border of these displays.
EXPECT_EQ(GetDisplayMatching(gfx::Rect(800, -100, 200, 200)).id(),
kPrimaryDisplayParams.sdk_display_id);
// Rectangle intersects with primary and external displays. If the
// intersection area is counted in pixel coordinates, the primary display will
// have the most intersection area.
EXPECT_EQ(GetDisplayMatching(gfx::Rect(800, -1000, 1100, 900)).id(),
kExternalDisplayParams.sdk_display_id);
// Rectangle is fully located inside second_external_display.
EXPECT_EQ(GetDisplayMatching(gfx::Rect(100, -1340, 700, 1240)).id(),
kExternalDisplayParams.sdk_display_id);
// Rectangle intersects with all displays. Display with the most
// intersection area should be returned.
EXPECT_EQ(GetDisplayMatching(gfx::Rect(-600, -300, 700, 600)).id(),
kSecondExternalDisplayParams.sdk_display_id);
}
} // namespace ui