blob: 7e2487d269d2b0eafdda42104bb923ac39e493ba [file] [log] [blame]
// Copyright 2014 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.
#ifndef ASH_WM_TABLET_MODE_TABLET_MODE_CONTROLLER_H_
#define ASH_WM_TABLET_MODE_TABLET_MODE_CONTROLLER_H_
#include <memory>
#include "ash/ash_export.h"
#include "ash/bluetooth_devices_observer.h"
#include "ash/display/window_tree_host_manager.h"
#include "ash/public/interfaces/tablet_mode.mojom.h"
#include "ash/session/session_observer.h"
#include "ash/shell_observer.h"
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/optional.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chromeos/accelerometer/accelerometer_reader.h"
#include "chromeos/accelerometer/accelerometer_types.h"
#include "chromeos/dbus/power_manager_client.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/bindings/interface_ptr_set.h"
#include "ui/events/devices/input_device_event_observer.h"
#include "ui/gfx/geometry/vector3d_f.h"
namespace aura {
class Window;
}
namespace base {
class TickClock;
}
namespace gfx {
class Vector3dF;
}
namespace views {
class Widget;
}
namespace ash {
class ScopedDisableInternalMouseAndKeyboard;
class TabletModeObserver;
class TabletModeWindowManager;
// TabletModeController listens to accelerometer events and automatically
// enters and exits tablet mode when the lid is opened beyond the triggering
// angle and rotates the display to match the device when in tablet mode.
class ASH_EXPORT TabletModeController
: public chromeos::AccelerometerReader::Observer,
public chromeos::PowerManagerClient::Observer,
public mojom::TabletModeController,
public ShellObserver,
public WindowTreeHostManager::Observer,
public SessionObserver,
public ui::InputDeviceEventObserver {
public:
// Used for keeping track if the user wants the machine to behave as a
// clamshell/tablet regardless of hardware orientation.
// TODO(oshima): Move this to common place.
enum class UiMode {
kNone = 0,
kClamshell,
kTabletMode,
};
// Public so it can be used by unit tests.
constexpr static char kLidAngleHistogramName[] = "Ash.TouchView.LidAngle";
TabletModeController();
~TabletModeController() override;
// TODO(jonross): Merge this with AttemptEnterTabletMode. Currently these are
// separate for several reasons: there is no internal display when running
// unittests; the event blocker prevents keyboard input when running ChromeOS
// on linux. http://crbug.com/362881
// Turn the always tablet mode window manager on or off.
// TODO(xdai): Make it a private function. This function is not supposed to be
// called by an external caller except for tests.
void EnableTabletModeWindowManager(bool should_enable);
// Test if the TabletModeWindowManager is enabled or not.
bool IsTabletModeWindowManagerEnabled() const;
// Add a special window to the TabletModeWindowManager for tracking. This is
// only required for special windows which are handled by other window
// managers like the |MultiUserWindowManager|.
// If the tablet mode is not enabled no action will be performed.
void AddWindow(aura::Window* window);
// Binds the mojom::TabletModeController interface request to this object.
void BindRequest(mojom::TabletModeControllerRequest request);
void AddObserver(TabletModeObserver* observer);
void RemoveObserver(TabletModeObserver* observer);
// Checks if we should auto hide title bars for the |widget| in tablet mode.
bool ShouldAutoHideTitlebars(views::Widget* widget);
// Whether the events from the internal mouse/keyboard are blocked.
bool AreInternalInputDeviceEventsBlocked() const;
// Flushes the mojo message pipe to chrome.
void FlushForTesting();
// If |record_lid_angle_timer_| is running, invokes its task and returns true.
// Otherwise, returns false.
bool TriggerRecordLidAngleTimerForTesting() WARN_UNUSED_RESULT;
// ShellObserver:
void OnShellInitialized() override;
// WindowTreeHostManager::Observer:
void OnDisplayConfigurationChanged() override;
// SessionObserver:
void OnChromeTerminating() override;
// chromeos::AccelerometerReader::Observer:
void OnAccelerometerUpdated(
scoped_refptr<const chromeos::AccelerometerUpdate> update) override;
// chromeos::PowerManagerClient::Observer:
void LidEventReceived(chromeos::PowerManagerClient::LidState state,
const base::TimeTicks& time) override;
void TabletModeEventReceived(chromeos::PowerManagerClient::TabletMode mode,
const base::TimeTicks& time) override;
void SuspendImminent(power_manager::SuspendImminent::Reason reason) override;
void SuspendDone(const base::TimeDelta& sleep_duration) override;
// ui::InputDeviceEventObserver:
void OnMouseDeviceConfigurationChanged() override;
void OnTouchpadDeviceConfigurationChanged() override;
void OnDeviceListsComplete() override;
private:
friend class TabletModeControllerTestApi;
// Used for recording metrics for intervals of time spent in
// and out of TabletMode.
enum TabletModeIntervalType {
TABLET_MODE_INTERVAL_INACTIVE,
TABLET_MODE_INTERVAL_ACTIVE
};
// Detect hinge rotation from base and lid accelerometers and automatically
// start / stop tablet mode.
void HandleHingeRotation(
scoped_refptr<const chromeos::AccelerometerUpdate> update);
void OnGetSwitchStates(
base::Optional<chromeos::PowerManagerClient::SwitchStates> result);
// Returns true if unstable lid angle can be used. The lid angle that falls in
// the unstable zone ([0, 20) and (340, 360] degrees) is considered unstable
// due to the potential erroneous accelerometer readings. Immediately using
// the unstable angle to trigger tablet mode is error-prone. So we wait for
// a certain range of time before using unstable angle.
bool CanUseUnstableLidAngle() const;
// True if it is possible to enter tablet mode in the current
// configuration. If this returns false, it should never be the case that
// tablet mode becomes enabled.
bool CanEnterTabletMode();
// Attempts to enter tablet mode and updates the internal keyboard and
// touchpad.
void AttemptEnterTabletMode();
// Attempts to exit tablet mode and updates the internal keyboard and
// touchpad.
void AttemptLeaveTabletMode();
// Record UMA stats tracking TabletMode usage. If |type| is
// TABLET_MODE_INTERVAL_INACTIVE, then record that TabletMode has been
// inactive from |tablet_mode_usage_interval_start_time_| until now.
// Similarly, record that TabletMode has been active if |type| is
// TABLET_MODE_INTERVAL_ACTIVE.
void RecordTabletModeUsageInterval(TabletModeIntervalType type);
// Reports an UMA histogram containing the value of |lid_angle_|.
// Called periodically by |record_lid_angle_timer_|.
void RecordLidAngle();
// Returns TABLET_MODE_INTERVAL_ACTIVE if TabletMode is currently active,
// otherwise returns TABLET_MODE_INTERNAL_INACTIVE.
TabletModeIntervalType CurrentTabletModeIntervalType();
// mojom::TabletModeController:
void SetClient(mojom::TabletModeClientPtr client) override;
// Checks whether we want to allow change the current ui mode to tablet mode
// or clamshell mode. This returns false if the user set a flag for the
// software to behave in a certain way regardless of configuration.
bool AllowUiModeChange() const;
// Called when a pointing device config is changed, or when a device list is
// sent from device manager. This will exit tablet mode if needed.
void HandlePointingDeviceAddedOrRemoved();
// Callback function for |bluetooth_devices_observer_|. Called when |device|
// changes.
void UpdateBluetoothDevice(device::BluetoothDevice* device);
// Update the internal mouse and keyboard event blocker |event_blocker_|
// according to current configuration. The internal input events should be
// blocked if 1) we are currently in tablet mode or 2) we are currently in
// laptop mode but the lid is flipped over (i.e., we are in laptop mode
// because of an external attached mouse).
void UpdateInternalMouseAndKeyboardEventBlocker();
// Returns true if the current lid angle can be detected and is in tablet mode
// angle range.
bool LidAngleIsInTabletModeRange();
// The maximized window manager (if enabled).
std::unique_ptr<TabletModeWindowManager> tablet_mode_window_manager_;
// A helper class which when instantiated will block native events from the
// internal keyboard and touchpad.
std::unique_ptr<ScopedDisableInternalMouseAndKeyboard> event_blocker_;
// Whether we have ever seen accelerometer data.
bool have_seen_accelerometer_data_ = false;
// Whether the lid angle can be detected. If it's true, the device is a
// convertible device (both screen acclerometer and keyboard acclerometer are
// available, thus lid angle is detectable). And if it's false, the device is
// either a laptop device or a tablet device (only the screen acclerometer is
// available).
bool can_detect_lid_angle_ = false;
// Tracks time spent in (and out of) tablet mode.
base::Time tablet_mode_usage_interval_start_time_;
base::TimeDelta total_tablet_mode_time_;
base::TimeDelta total_non_tablet_mode_time_;
// Tracks the first time the lid angle was unstable. This is used to suppress
// erroneous accelerometer readings as the lid is nearly opened or closed but
// the accelerometer reports readings that make the lid to appear near fully
// open. (e.g. After closing the lid, the correct angle reading is 0. But the
// accelerometer may report 359.5 degrees which triggers the tablet mode by
// mistake.)
base::TimeTicks first_unstable_lid_angle_time_;
// Source for the current time in base::TimeTicks.
const base::TickClock* tick_clock_;
// Set when tablet mode switch is on. This is used to force tablet mode.
bool tablet_mode_switch_is_on_ = false;
// Tracks when the lid is closed. Used to prevent entering tablet mode.
bool lid_is_closed_ = false;
// Last computed lid angle.
double lid_angle_ = 0.0f;
// Tracks if the device has an external pointing device. The device will
// not enter tablet mode if this is true.
bool has_external_pointing_device_ = false;
// Tracks smoothed accelerometer data over time. This is done when the hinge
// is approaching vertical to remove abrupt acceleration that can lead to
// incorrect calculations of hinge angles.
gfx::Vector3dF base_smoothed_;
gfx::Vector3dF lid_smoothed_;
// Binding for the TabletModeController interface.
mojo::Binding<mojom::TabletModeController> binding_;
// Client interface (e.g. in chrome).
mojom::TabletModeClientPtr client_;
// Tracks whether a flag is used to force ui mode.
UiMode force_ui_mode_ = UiMode::kNone;
// Calls RecordLidAngle() periodically.
base::RepeatingTimer record_lid_angle_timer_;
ScopedSessionObserver scoped_session_observer_;
// Observer to observe the bluetooth devices.
std::unique_ptr<BluetoothDevicesObserver> bluetooth_devices_observer_;
base::ObserverList<TabletModeObserver>::Unchecked tablet_mode_observers_;
base::WeakPtrFactory<TabletModeController> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(TabletModeController);
};
} // namespace ash
#endif // ASH_WM_TABLET_MODE_TABLET_MODE_CONTROLLER_H_