| // Copyright 2015 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 "ui/base/material_design/material_design_controller.h" |
| |
| #include <string> |
| |
| #include "base/command_line.h" |
| #include "base/feature_list.h" |
| #include "base/logging.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/trace_event/trace_event.h" |
| #include "build/build_config.h" |
| #include "build/buildflag.h" |
| #include "ui/base/ui_base_features.h" |
| #include "ui/base/ui_base_switches.h" |
| #include "ui/base/ui_features.h" |
| #include "ui/gfx/animation/linear_animation.h" |
| #include "ui/gfx/color_palette.h" |
| #include "ui/gfx/color_utils.h" |
| |
| #if defined(OS_CHROMEOS) |
| #include <fcntl.h> |
| |
| #include "base/files/file_enumerator.h" |
| #include "base/files/scoped_file.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "ui/base/touch/touch_device.h" |
| #include "ui/events/devices/device_data_manager.h" |
| #include "ui/events/ozone/evdev/event_device_info.h" // nogncheck |
| #endif // defined(OS_CHROMEOS) |
| |
| #if defined(OS_WIN) |
| #include "base/win/win_util.h" |
| #include "ui/base/win/hidden_window.h" |
| #endif |
| |
| namespace ui { |
| namespace { |
| |
| #if defined(OS_CHROMEOS) || defined(OS_WIN) || defined(OS_LINUX) |
| |
| // Whether Material Refresh should be used by default. |
| // Material refresh is controlled by both --top-chrome-md and this feature. |
| // --top-chrome-md should take precedence over what this feature may indicate. |
| bool IsMaterialRefreshEnabled() { |
| static constexpr base::Feature kMaterialRefreshEnabledFeature = { |
| "MaterialRefresh", base::FEATURE_ENABLED_BY_DEFAULT}; |
| return base::FeatureList::IsEnabled(kMaterialRefreshEnabledFeature); |
| } |
| |
| #endif |
| |
| #if defined(OS_CHROMEOS) |
| |
| // Whether to use MATERIAL_TOUCH_OPTIMIZED when a touch device is detected. |
| // Enabled by default on ChromeOS. |
| const base::Feature kTouchOptimizedUi = {"TouchOptimizedUi", |
| base::FEATURE_ENABLED_BY_DEFAULT}; |
| |
| MaterialDesignController::Mode GetDefaultTouchDeviceMode() { |
| bool material_refresh_enabled = IsMaterialRefreshEnabled(); |
| bool touch_optimized_ui_enabled = |
| base::FeatureList::IsEnabled(kTouchOptimizedUi); |
| if (material_refresh_enabled) { |
| return touch_optimized_ui_enabled |
| ? MaterialDesignController::MATERIAL_TOUCH_REFRESH |
| : MaterialDesignController::MATERIAL_REFRESH; |
| } |
| |
| return touch_optimized_ui_enabled |
| ? MaterialDesignController::MATERIAL_TOUCH_OPTIMIZED |
| : MaterialDesignController::MATERIAL_HYBRID; |
| } |
| |
| bool HasTouchscreen() { |
| // If a scan of available devices has already completed, use that. |
| if (DeviceDataManager::HasInstance() && |
| DeviceDataManager::GetInstance()->AreDeviceListsComplete()) |
| return GetTouchScreensAvailability() == TouchScreensAvailability::ENABLED; |
| |
| // Otherwise perform our own scan to determine the presence of a touchscreen. |
| // Note this is a one-time call that occurs during device startup or restart. |
| base::FileEnumerator file_enum( |
| base::FilePath(FILE_PATH_LITERAL("/dev/input")), false, |
| base::FileEnumerator::FILES, FILE_PATH_LITERAL("event*[0-9]")); |
| for (base::FilePath path = file_enum.Next(); !path.empty(); |
| path = file_enum.Next()) { |
| EventDeviceInfo devinfo; |
| base::ScopedFD fd( |
| open(path.value().c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC)); |
| if (fd.is_valid() && devinfo.Initialize(fd.get(), path) && |
| devinfo.HasTouchscreen()) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| #endif // OS_CHROMEOS |
| |
| } // namespace |
| |
| bool MaterialDesignController::is_mode_initialized_ = false; |
| |
| MaterialDesignController::Mode MaterialDesignController::mode_ = |
| MaterialDesignController::MATERIAL_NORMAL; |
| |
| // static |
| void MaterialDesignController::Initialize() { |
| TRACE_EVENT0("startup", "MaterialDesignController::InitializeMode"); |
| CHECK(!is_mode_initialized_); |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| const std::string switch_value = |
| command_line->GetSwitchValueASCII(switches::kTopChromeMD); |
| |
| bool force_material_refresh = false; |
| #if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX) |
| force_material_refresh = |
| base::FeatureList::IsEnabled(features::kExperimentalUi); |
| #endif |
| |
| if (force_material_refresh || |
| switch_value == switches::kTopChromeMDMaterialRefresh) { |
| SetMode(MATERIAL_REFRESH); |
| } else if (switch_value == switches::kTopChromeMDMaterial) { |
| SetMode(MATERIAL_NORMAL); |
| } else if (switch_value == switches::kTopChromeMDMaterialHybrid) { |
| SetMode(MATERIAL_HYBRID); |
| } else if (switch_value == switches::kTopChromeMDMaterialTouchOptimized) { |
| SetMode(MATERIAL_TOUCH_OPTIMIZED); |
| } else if (switch_value == |
| switches::kTopChromeMDMaterialRefreshTouchOptimized) { |
| SetMode(MATERIAL_TOUCH_REFRESH); |
| } else if (switch_value == switches::kTopChromeMDMaterialAuto) { |
| #if defined(OS_WIN) |
| // TODO(girard): add support for switching between modes when |
| // the device switches to "tablet mode". |
| if (base::win::IsTabletDevice(nullptr, ui::GetHiddenWindow())) |
| SetMode(MATERIAL_HYBRID); |
| #endif |
| SetMode(DefaultMode()); |
| } else { |
| if (!switch_value.empty()) { |
| LOG(ERROR) << "Invalid value='" << switch_value |
| << "' for command line switch '" << switches::kTopChromeMD |
| << "'."; |
| } |
| SetMode(DefaultMode()); |
| } |
| |
| // Ideally, there would be a more general, "initialize random stuff here" |
| // function into which these things and a call to this function can be placed. |
| // TODO(crbug.com/864544) |
| if (IsRefreshUi()) |
| color_utils::SetDarkestColor(gfx::kGoogleGrey900); |
| |
| double animation_duration_scale; |
| if (base::StringToDouble( |
| command_line->GetSwitchValueASCII(switches::kAnimationDurationScale), |
| &animation_duration_scale)) { |
| gfx::LinearAnimation::SetDurationScale(animation_duration_scale); |
| } |
| } |
| |
| // static |
| MaterialDesignController::Mode MaterialDesignController::GetMode() { |
| CHECK(is_mode_initialized_); |
| return mode_; |
| } |
| |
| // static |
| bool MaterialDesignController::IsSecondaryUiMaterial() { |
| return base::FeatureList::IsEnabled(features::kSecondaryUiMd) || |
| IsRefreshUi(); |
| } |
| |
| // static |
| bool MaterialDesignController::IsTouchOptimizedUiEnabled() { |
| return GetMode() == MATERIAL_TOUCH_OPTIMIZED || |
| GetMode() == MATERIAL_TOUCH_REFRESH; |
| } |
| |
| // static |
| bool MaterialDesignController::IsNewerMaterialUi() { |
| return IsTouchOptimizedUiEnabled() || IsRefreshUi(); |
| } |
| |
| // static |
| bool MaterialDesignController::IsRefreshUi() { |
| return GetMode() == MATERIAL_REFRESH || GetMode() == MATERIAL_TOUCH_REFRESH; |
| } |
| |
| // static |
| MaterialDesignController::Mode MaterialDesignController::DefaultMode() { |
| #if defined(OS_CHROMEOS) |
| // This is called (once) early in device startup to initialize core UI, so |
| // the UI thread should be blocked to perform the device query. |
| base::ScopedAllowBlocking allow_io; |
| if (HasTouchscreen()) |
| return GetDefaultTouchDeviceMode(); |
| |
| return IsMaterialRefreshEnabled() ? MATERIAL_REFRESH : MATERIAL_NORMAL; |
| #endif // defined(OS_CHROMEOS) |
| |
| #if defined(OS_WIN) || defined(OS_LINUX) |
| return IsMaterialRefreshEnabled() ? MATERIAL_REFRESH : MATERIAL_NORMAL; |
| #elif defined(OS_MACOSX) && BUILDFLAG(MAC_VIEWS_BROWSER) |
| return features::IsViewsBrowserCocoa() ? MATERIAL_NORMAL : MATERIAL_REFRESH; |
| #else |
| return MATERIAL_NORMAL; |
| #endif |
| } |
| |
| // static |
| void MaterialDesignController::Uninitialize() { |
| is_mode_initialized_ = false; |
| } |
| |
| // static |
| void MaterialDesignController::SetMode(MaterialDesignController::Mode mode) { |
| mode_ = mode; |
| is_mode_initialized_ = true; |
| } |
| |
| } // namespace ui |