Support showing alignment hint based on aligniment state of dock

Bug: 141984730
Test: atest SystemUITests:KeyguardIndicationControllerTest
Change-Id: Ic0911683725a0e54406df06b948169c4dde94764
Merged-In: Ic0911683725a0e54406df06b948169c4dde94764
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6ffea1b..6e578bf 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -964,6 +964,12 @@
     <!-- Message shown when face authentication fails and the pin pad is visible. [CHAR LIMIT=60] -->
     <string name="keyguard_retry">Swipe up to try again</string>
 
+    <!-- Indication when device is slow charging due to misalignment on the dock. [CHAR LIMIT=60] -->
+    <string name="dock_alignment_slow_charging" product="default">Realign phone for faster charging</string>
+
+    <!-- Indication when device is not charging due to bad placement on the dock. [CHAR LIMIT=60] -->
+    <string name="dock_alignment_not_charging" product="default">Realign phone to charge wirelessly</string>
+
     <!-- Text on keyguard screen and in Quick Settings footer indicating that the device is enterprise-managed by a Device Owner [CHAR LIMIT=60] -->
     <string name="do_disclosure_generic">This device is managed by your organization</string>
 
diff --git a/packages/SystemUI/src/com/android/systemui/dock/DockManager.java b/packages/SystemUI/src/com/android/systemui/dock/DockManager.java
index d332f59..42e065a 100644
--- a/packages/SystemUI/src/com/android/systemui/dock/DockManager.java
+++ b/packages/SystemUI/src/com/android/systemui/dock/DockManager.java
@@ -17,12 +17,12 @@
 package com.android.systemui.dock;
 
 /**
- * Allows an app to handle dock events
+ * Allows an app to handle dock events.
  */
 public interface DockManager {
 
     /**
-     * Uninitialized / undocking dock states
+     * Uninitialized / undocking dock states.
      */
     int STATE_NONE = 0;
     /**
@@ -30,34 +30,75 @@
      */
     int STATE_DOCKED = 1;
     /**
-     * The state for docking without showing UI
+     * The state for docking without showing UI.
      */
     int STATE_DOCKED_HIDE = 2;
 
     /**
-     * Add a dock event listener into manager
+     * Indicates there's no alignment issue.
+     */
+    int ALIGN_STATE_GOOD = 0;
+
+    /**
+     * Indicates it's slightly not aligned with dock. Normally combines with slow charging issue.
+     */
+    int ALIGN_STATE_POOR = 1;
+
+    /**
+     * Indicates it's not aligned with dock. Normally combines with not charging issue.
+     */
+    int ALIGN_STATE_TERRIBLE = 2;
+
+    /**
+     * Adds a dock event listener into manager.
      *
      * @param callback A {@link DockEventListener} which want to add
      */
     void addListener(DockEventListener callback);
 
     /**
-     * Remove the added listener from dock manager
+     * Removes the added listener from dock manager
      *
      * @param callback A {@link DockEventListener} which want to remove
      */
     void removeListener(DockEventListener callback);
 
     /**
+     * Adds a alignment listener into manager.
+     *
+     * @param listener A {@link AlignmentStateListener} which want to add
+     */
+    void addAlignmentStateListener(AlignmentStateListener listener);
+
+    /**
+     * Removes the added alignment listener from dock manager.
+     *
+     * @param listener A {@link AlignmentStateListener} which want to remove
+     */
+    void removeAlignmentStateListener(AlignmentStateListener listener);
+
+    /**
     * Returns true if the device is in docking state.
     */
     boolean isDocked();
 
-    /** Callback for receiving dock events */
+    /**
+     * Listens to dock events.
+     */
     interface DockEventListener {
         /**
-         * Override to handle dock events
+         * Override to handle dock events.
          */
         void onEvent(int event);
     }
+
+    /**
+     * Listens to dock alignment state changed.
+     */
+    interface AlignmentStateListener {
+        /**
+         * Override to handle alignment state changes.
+         */
+        void onAlignmentStateChanged(int alignState);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dock/DockManagerImpl.java b/packages/SystemUI/src/com/android/systemui/dock/DockManagerImpl.java
index fa7f503..f6d24e8 100644
--- a/packages/SystemUI/src/com/android/systemui/dock/DockManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/dock/DockManagerImpl.java
@@ -35,6 +35,14 @@
     }
 
     @Override
+    public void addAlignmentStateListener(AlignmentStateListener listener) {
+    }
+
+    @Override
+    public void removeAlignmentStateListener(AlignmentStateListener listener) {
+    }
+
+    @Override
     public boolean isDocked() {
         return false;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index f3ae50b..dd5255a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -50,6 +50,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.dock.DockManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
@@ -96,6 +97,7 @@
     private final IBatteryStats mBatteryInfo;
     private final SettableWakeLock mWakeLock;
     private final LockPatternUtils mLockPatternUtils;
+    private final DockManager mDockManager;
 
     private final int mSlowThreshold;
     private final int mFastThreshold;
@@ -104,6 +106,7 @@
     private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
 
     private String mRestingIndication;
+    private String mAlignmentIndication = "";
     private CharSequence mTransientIndication;
     private ColorStateList mTransientTextColorState;
     private ColorStateList mInitialTextColorState;
@@ -141,7 +144,8 @@
                 Dependency.get(AccessibilityController.class),
                 UnlockMethodCache.getInstance(context),
                 Dependency.get(StatusBarStateController.class),
-                KeyguardUpdateMonitor.getInstance(context));
+                Dependency.get(KeyguardUpdateMonitor.class),
+                Dependency.get(DockManager.class));
     }
 
     /**
@@ -152,7 +156,8 @@
             LockPatternUtils lockPatternUtils, WakeLock wakeLock, ShadeController shadeController,
             AccessibilityController accessibilityController, UnlockMethodCache unlockMethodCache,
             StatusBarStateController statusBarStateController,
-            KeyguardUpdateMonitor keyguardUpdateMonitor) {
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            DockManager dockManager) {
         mContext = context;
         mLockIcon = lockIcon;
         mShadeController = shadeController;
@@ -160,6 +165,8 @@
         mUnlockMethodCache = unlockMethodCache;
         mStatusBarStateController = statusBarStateController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mDockManager = dockManager;
+        mDockManager.addAlignmentStateListener(this::handleAlignStateChanged);
         // lock icon is not used on all form factors.
         if (mLockIcon != null) {
             mLockIcon.setOnLongClickListener(this::handleLockLongClick);
@@ -213,6 +220,21 @@
         mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
     }
 
+    private void handleAlignStateChanged(int alignState) {
+        String alignmentIndication = "";
+        if (alignState == DockManager.ALIGN_STATE_POOR) {
+            alignmentIndication =
+                    mContext.getResources().getString(R.string.dock_alignment_slow_charging);
+        } else if (alignState == DockManager.ALIGN_STATE_TERRIBLE) {
+            alignmentIndication =
+                    mContext.getResources().getString(R.string.dock_alignment_not_charging);
+        }
+        if (!alignmentIndication.equals(mAlignmentIndication)) {
+            mAlignmentIndication = alignmentIndication;
+            updateIndication(false);
+        }
+    }
+
     /**
      * Gets the {@link KeyguardUpdateMonitorCallback} instance associated with this
      * {@link KeyguardIndicationController}.
@@ -256,7 +278,7 @@
         if (visible) {
             // If this is called after an error message was already shown, we should not clear it.
             // Otherwise the error message won't be shown
-            if  (!mHandler.hasMessages(MSG_HIDE_TRANSIENT)) {
+            if (!mHandler.hasMessages(MSG_HIDE_TRANSIENT)) {
                 hideTransientIndication();
             }
             updateIndication(false);
@@ -367,6 +389,9 @@
                 mTextView.setTextColor(Color.WHITE);
                 if (!TextUtils.isEmpty(mTransientIndication)) {
                     mTextView.switchIndication(mTransientIndication);
+                } else if (!TextUtils.isEmpty(mAlignmentIndication)) {
+                    mTextView.switchIndication(mAlignmentIndication);
+                    mTextView.setTextColor(Utils.getColorError(mContext));
                 } else if (mPowerPluggedIn) {
                     String indication = computePowerIndication();
                     if (animate) {
@@ -395,6 +420,9 @@
                     && mKeyguardUpdateMonitor.getUserHasTrust(userId)) {
                 mTextView.switchIndication(trustGrantedIndication);
                 mTextView.setTextColor(mInitialTextColorState);
+            } else if (!TextUtils.isEmpty(mAlignmentIndication)) {
+                mTextView.switchIndication(mAlignmentIndication);
+                mTextView.setTextColor(Utils.getColorError(mContext));
             } else if (mPowerPluggedIn) {
                 String indication = computePowerIndication();
                 if (DEBUG_CHARGING_SPEED) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java b/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java
index 839b5e4..fd48d34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dock/DockManagerFake.java
@@ -21,6 +21,7 @@
  */
 public class DockManagerFake implements DockManager {
     DockEventListener mCallback;
+    AlignmentStateListener mAlignmentListener;
 
     @Override
     public void addListener(DockEventListener callback) {
@@ -33,6 +34,16 @@
     }
 
     @Override
+    public void addAlignmentStateListener(AlignmentStateListener listener) {
+        mAlignmentListener = listener;
+    }
+
+    @Override
+    public void removeAlignmentStateListener(AlignmentStateListener listener) {
+        mAlignmentListener = listener;
+    }
+
+    @Override
     public boolean isDocked() {
         return false;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index c85e515..2fe51d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -50,8 +50,10 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dock.DockManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
 import com.android.systemui.statusbar.phone.LockIcon;
@@ -65,6 +67,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -100,6 +103,10 @@
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     @Mock
     private UserManager mUserManager;
+    @Mock
+    private DockManager mDockManager;
+    @Captor
+    private ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener;
     private KeyguardIndicationTextView mTextView;
 
     private KeyguardIndicationController mController;
@@ -135,7 +142,8 @@
         }
         mController = new KeyguardIndicationController(mContext, mIndicationArea, mLockIcon,
                 mLockPatternUtils, mWakeLock, mShadeController, mAccessibilityController,
-                mUnlockMethodCache, mStatusBarStateController, mKeyguardUpdateMonitor);
+                mUnlockMethodCache, mStatusBarStateController, mKeyguardUpdateMonitor,
+                mDockManager);
         mController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
     }
 
@@ -206,6 +214,74 @@
     }
 
     @Test
+    public void createController_addsAlignmentListener() {
+        createController();
+
+        verify(mDockManager).addAlignmentStateListener(
+                any(DockManager.AlignmentStateListener.class));
+    }
+
+    @Test
+    public void onAlignmentStateChanged_showsSlowChargingIndication() {
+        createController();
+        verify(mDockManager).addAlignmentStateListener(mAlignmentListener.capture());
+        mController.setVisible(true);
+
+        mAlignmentListener.getValue().onAlignmentStateChanged(
+                DockManager.ALIGN_STATE_POOR);
+
+        assertThat(mTextView.getText()).isEqualTo(
+                mContext.getResources().getString(R.string.dock_alignment_slow_charging));
+        assertThat(mTextView.getCurrentTextColor()).isEqualTo(
+                Utils.getColorError(mContext).getDefaultColor());
+    }
+
+    @Test
+    public void onAlignmentStateChanged_showsNotChargingIndication() {
+        createController();
+        verify(mDockManager).addAlignmentStateListener(mAlignmentListener.capture());
+        mController.setVisible(true);
+
+        mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_TERRIBLE);
+
+        assertThat(mTextView.getText()).isEqualTo(
+                mContext.getResources().getString(R.string.dock_alignment_not_charging));
+        assertThat(mTextView.getCurrentTextColor()).isEqualTo(
+                Utils.getColorError(mContext).getDefaultColor());
+    }
+
+    @Test
+    public void onAlignmentStateChanged_whileDozing_showsSlowChargingIndication() {
+        createController();
+        verify(mDockManager).addAlignmentStateListener(mAlignmentListener.capture());
+        mController.setVisible(true);
+        mController.setDozing(true);
+
+        mAlignmentListener.getValue().onAlignmentStateChanged(
+                DockManager.ALIGN_STATE_POOR);
+
+        assertThat(mTextView.getText()).isEqualTo(
+                mContext.getResources().getString(R.string.dock_alignment_slow_charging));
+        assertThat(mTextView.getCurrentTextColor()).isEqualTo(
+                Utils.getColorError(mContext).getDefaultColor());
+    }
+
+    @Test
+    public void onAlignmentStateChanged_whileDozing_showsNotChargingIndication() {
+        createController();
+        verify(mDockManager).addAlignmentStateListener(mAlignmentListener.capture());
+        mController.setVisible(true);
+        mController.setDozing(true);
+
+        mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_TERRIBLE);
+
+        assertThat(mTextView.getText()).isEqualTo(
+                mContext.getResources().getString(R.string.dock_alignment_not_charging));
+        assertThat(mTextView.getCurrentTextColor()).isEqualTo(
+                Utils.getColorError(mContext).getDefaultColor());
+    }
+
+    @Test
     public void transientIndication_holdsWakeLock_whenDozing() {
         createController();