Fix entering mirror mode on docked

Background:
Mirror mode is turned on upon entering tablet mode. Tablet mode is
entered when lid angle is larger than 200 degree. However, closing lid
triggers erroneous accelerometer reading which makes the lid appear to
be fully open (e.g. 359 degree). So mirror mode is incorrectly turned
on.

Changes:
Ignore unstable lid angle for 2 seconds and only use unstable lid
angle to turn on/off tablet mode after that.

Bug: 790720

TBR=afakhry@chromium.org,skuhne@chromium.org

(cherry picked from commit 208b44ca364b93b73286b3364f1700009197afc6)

Change-Id: I1d9fa6394350303730062136c7bfe7a5a6fe98f5
Reviewed-on: https://chromium-review.googlesource.com/814936
Reviewed-by: Robert Flack <flackr@chromium.org>
Reviewed-by: Ahmed Fakhry <afakhry@chromium.org>
Reviewed-by: Stefan Kuhne <skuhne@chromium.org>
Commit-Queue: Weidong Guo <weidongg@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#535181}
Reviewed-on: https://chromium-review.googlesource.com/927213
Reviewed-by: Weidong Guo <weidongg@chromium.org>
Cr-Commit-Position: refs/branch-heads/3325@{#559}
Cr-Branched-From: bc084a8b5afa3744a74927344e304c02ae54189f-refs/heads/master@{#530369}
diff --git a/ash/wm/tablet_mode/tablet_mode_controller.cc b/ash/wm/tablet_mode/tablet_mode_controller.cc
index 58b080f..beb2474 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller.cc
+++ b/ash/wm/tablet_mode/tablet_mode_controller.cc
@@ -46,11 +46,12 @@
 const float kMinStableAngle = 20.0f;
 const float kMaxStableAngle = 340.0f;
 
-// The time duration to consider the lid to be recently opened.
-// This is used to prevent entering tablet mode if an erroneous accelerometer
-// reading makes the lid appear to be fully open when the user is opening the
-// lid from a closed position.
-const int kLidRecentlyOpenedDurationSeconds = 2;
+// The time duration to consider an unstable lid angle to be valid. This is used
+// to prevent entering tablet mode if an erroneous accelerometer reading makes
+// the lid appear to be fully open when the user is opening the lid from a
+// closed position or is closing the lid from an opened position.
+constexpr base::TimeDelta kUnstableLidAngleDuration =
+    base::TimeDelta::FromSeconds(2);
 
 // When the device approaches vertical orientation (i.e. portrait orientation)
 // the accelerometers for the base and lid approach the same values (i.e.
@@ -261,8 +262,6 @@
     return;
 
   const bool open = state == chromeos::PowerManagerClient::LidState::OPEN;
-  if (open)
-    last_lid_open_time_ = time;
   lid_is_closed_ = !open;
   LeaveTabletMode();
 }
@@ -355,11 +354,13 @@
   bool is_angle_stable = is_angle_reliable && lid_angle >= kMinStableAngle &&
                          lid_angle <= kMaxStableAngle;
 
-  // Clear the last_lid_open_time_ for a stable reading so that there is less
-  // chance of a delay if the lid is moved from the close state to the fully
-  // open state very quickly.
-  if (is_angle_stable)
-    last_lid_open_time_ = base::TimeTicks();
+  if (is_angle_stable) {
+    // Reset the timestamp of first unstable lid angle because we get a stable
+    // reading.
+    first_unstable_lid_angle_time_ = base::TimeTicks();
+  } else if (first_unstable_lid_angle_time_.is_null()) {
+    first_unstable_lid_angle_time_ = tick_clock_->NowTicks();
+  }
 
   // Toggle tablet mode on or off when corresponding thresholds are passed.
   if (IsTabletModeWindowManagerEnabled() && is_angle_stable &&
@@ -367,7 +368,7 @@
     LeaveTabletMode();
   } else if (!IsTabletModeWindowManagerEnabled() && !lid_is_closed_ &&
              lid_angle >= kEnterTabletModeAngle &&
-             (is_angle_stable || !WasLidOpenedRecently())) {
+             (is_angle_stable || CanUseUnstableLidAngle())) {
     EnterTabletMode();
   }
 }
@@ -482,14 +483,13 @@
   TabletModeEventReceived(result->tablet_mode, base::TimeTicks::Now());
 }
 
-bool TabletModeController::WasLidOpenedRecently() const {
-  if (last_lid_open_time_.is_null())
-    return false;
+bool TabletModeController::CanUseUnstableLidAngle() const {
+  DCHECK(!first_unstable_lid_angle_time_.is_null());
 
-  base::TimeTicks now = tick_clock_->NowTicks();
-  DCHECK(now >= last_lid_open_time_);
-  base::TimeDelta elapsed_time = now - last_lid_open_time_;
-  return elapsed_time.InSeconds() <= kLidRecentlyOpenedDurationSeconds;
+  const base::TimeTicks now = tick_clock_->NowTicks();
+  DCHECK(now >= first_unstable_lid_angle_time_);
+  const base::TimeDelta elapsed_time = now - first_unstable_lid_angle_time_;
+  return elapsed_time >= kUnstableLidAngleDuration;
 }
 
 void TabletModeController::SetTickClockForTest(
diff --git a/ash/wm/tablet_mode/tablet_mode_controller.h b/ash/wm/tablet_mode/tablet_mode_controller.h
index 80f2668..9e28855 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller.h
+++ b/ash/wm/tablet_mode/tablet_mode_controller.h
@@ -150,8 +150,12 @@
   void OnGetSwitchStates(
       base::Optional<chromeos::PowerManagerClient::SwitchStates> result);
 
-  // Returns true if the lid was recently opened.
-  bool WasLidOpenedRecently() const;
+  // 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;
 
   // Enables TabletModeWindowManager, and determines the current state of
   // rotation lock.
@@ -198,10 +202,13 @@
   base::TimeDelta total_tabletmode_time_;
   base::TimeDelta total_non_tabletmode_time_;
 
-  // Tracks the last time we received a lid open event. This is used to suppress
-  // erroneous accelerometer readings as the lid is opened but the accelerometer
-  // reports readings that make the lid to appear near fully open.
-  base::TimeTicks last_lid_open_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.
   std::unique_ptr<base::TickClock> tick_clock_;
diff --git a/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc b/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
index 40935ce..c63ac6c 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
+++ b/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
@@ -160,8 +160,8 @@
         tablet_mode_controller()->tick_clock_->NowTicks());
   }
 
-  bool WasLidOpenedRecently() {
-    return tablet_mode_controller()->WasLidOpenedRecently();
+  bool CanUseUnstableLidAngle() {
+    return tablet_mode_controller()->CanUseUnstableLidAngle();
   }
 
   void SetTabletMode(bool on) {
@@ -239,49 +239,40 @@
   EXPECT_FALSE(IsTabletModeStarted());
 }
 
-// Verify the tablet mode state for unstable hinge angles when the lid was
-// recently open.
-TEST_F(TabletModeControllerTest, UnstableHingeAnglesWhenLidRecentlyOpened) {
+// Verify the unstable lid angle is suppressed during opening the lid.
+TEST_F(TabletModeControllerTest, OpenLidUnstableLidAngle) {
   AttachTickClockForTest();
 
   OpenLid();
-  ASSERT_TRUE(WasLidOpenedRecently());
 
+  // Simulate the erroneous accelerometer readings.
+  OpenLidToAngle(355.0f);
+  EXPECT_FALSE(IsTabletModeStarted());
+
+  // Simulate the correct accelerometer readings.
+  OpenLidToAngle(5.0f);
+  EXPECT_FALSE(IsTabletModeStarted());
+}
+
+// Verify the unstable lid angle is suppressed during closing the lid.
+TEST_F(TabletModeControllerTest, CloseLidUnstableLidAngle) {
+  AttachTickClockForTest();
+
+  OpenLid();
+
+  OpenLidToAngle(45.0f);
+  EXPECT_FALSE(IsTabletModeStarted());
+
+  // Simulate the correct accelerometer readings.
   OpenLidToAngle(5.0f);
   EXPECT_FALSE(IsTabletModeStarted());
 
+  // Simulate the erroneous accelerometer readings.
   OpenLidToAngle(355.0f);
   EXPECT_FALSE(IsTabletModeStarted());
 
-  // This is a stable reading and should clear the last lid opened time.
-  OpenLidToAngle(45.0f);
-  EXPECT_FALSE(IsTabletModeStarted());
-  EXPECT_FALSE(WasLidOpenedRecently());
-
-  OpenLidToAngle(355.0f);
-  EXPECT_TRUE(IsTabletModeStarted());
-}
-
-// Verify the WasLidOpenedRecently signal with respect to time.
-TEST_F(TabletModeControllerTest, WasLidOpenedRecentlyOverTime) {
-  AttachTickClockForTest();
-
-  // No lid open time initially.
-  ASSERT_FALSE(WasLidOpenedRecently());
-
   CloseLid();
-  EXPECT_FALSE(WasLidOpenedRecently());
-
-  OpenLid();
-  EXPECT_TRUE(WasLidOpenedRecently());
-
-  // 1 second after lid open.
-  AdvanceTickClock(base::TimeDelta::FromSeconds(1));
-  EXPECT_TRUE(WasLidOpenedRecently());
-
-  // 3 seconds after lid open.
-  AdvanceTickClock(base::TimeDelta::FromSeconds(2));
-  EXPECT_FALSE(WasLidOpenedRecently());
+  EXPECT_FALSE(IsTabletModeStarted());
 }
 
 TEST_F(TabletModeControllerTest, TabletModeTransition) {
@@ -333,7 +324,6 @@
 // Verify the tablet mode enter/exit thresholds for stable angles.
 TEST_F(TabletModeControllerTest, StableHingeAnglesWithLidOpened) {
   ASSERT_FALSE(IsTabletModeStarted());
-  ASSERT_FALSE(WasLidOpenedRecently());
 
   OpenLidToAngle(180.0f);
   EXPECT_FALSE(IsTabletModeStarted());
@@ -354,20 +344,59 @@
   EXPECT_FALSE(IsTabletModeStarted());
 }
 
-// Verify the tablet mode state for unstable hinge angles when the lid is open
-// but not recently.
-TEST_F(TabletModeControllerTest, UnstableHingeAnglesWithLidOpened) {
+// Verify entering tablet mode for unstable lid angles when a certain range of
+// time has passed.
+TEST_F(TabletModeControllerTest, EnterTabletModeWithUnstableLidAngle) {
   AttachTickClockForTest();
 
-  ASSERT_FALSE(WasLidOpenedRecently());
+  OpenLid();
+
   ASSERT_FALSE(IsTabletModeStarted());
 
   OpenLidToAngle(5.0f);
   EXPECT_FALSE(IsTabletModeStarted());
 
+  EXPECT_FALSE(CanUseUnstableLidAngle());
+  OpenLidToAngle(355.0f);
+  EXPECT_FALSE(IsTabletModeStarted());
+
+  // 1 second after entering unstable angle zone.
+  AdvanceTickClock(base::TimeDelta::FromSeconds(1));
+  EXPECT_FALSE(CanUseUnstableLidAngle());
+  OpenLidToAngle(355.0f);
+  EXPECT_FALSE(IsTabletModeStarted());
+
+  // 2 seconds after entering unstable angle zone.
+  AdvanceTickClock(base::TimeDelta::FromSeconds(1));
+  EXPECT_TRUE(CanUseUnstableLidAngle());
   OpenLidToAngle(355.0f);
   EXPECT_TRUE(IsTabletModeStarted());
+}
 
+// Verify not exiting tablet mode for unstable lid angles even after a certain
+// range of time has passed.
+TEST_F(TabletModeControllerTest, NotExitTabletModeWithUnstableLidAngle) {
+  AttachTickClockForTest();
+
+  OpenLid();
+
+  ASSERT_FALSE(IsTabletModeStarted());
+
+  OpenLidToAngle(280.0f);
+  EXPECT_TRUE(IsTabletModeStarted());
+
+  OpenLidToAngle(5.0f);
+  EXPECT_TRUE(IsTabletModeStarted());
+
+  // 1 second after entering unstable angle zone.
+  AdvanceTickClock(base::TimeDelta::FromSeconds(1));
+  EXPECT_FALSE(CanUseUnstableLidAngle());
+  OpenLidToAngle(5.0f);
+  EXPECT_TRUE(IsTabletModeStarted());
+
+  // 2 seconds after entering unstable angle zone.
+  AdvanceTickClock(base::TimeDelta::FromSeconds(1));
+  EXPECT_TRUE(CanUseUnstableLidAngle());
   OpenLidToAngle(5.0f);
   EXPECT_TRUE(IsTabletModeStarted());
 }