Adjustments to handles visuals and behavior

- Fix display on devices with larger corners by resizing the handle
views as the corner radius changes.
- Fix home x assist handle color matching by ensuring `isListening` is
set to after color matching start
- Allow UiController to suppress handles showing up during assist
sessions by calling ScreenDecorations.setAssistHintBlocked
- Add animation curve from spec to handle scale animation
- Fix handle rounded ends
- Make handle dimensions customizable

jamesoleary 2 CLs - ag/7727795, ag/7727797 (fix test failure)
mrcasey 2 CLs - ag/7704742, ag/7713549

Test: Verify WAI
Bug: 130642504
Change-Id: I9563d7f437064c8c63c988a9fda41be906064219
diff --git a/packages/SystemUI/res/drawable/corner_gesture_hint.xml b/packages/SystemUI/res/drawable/corner_gesture_hint.xml
deleted file mode 100644
index 3f4abb0..0000000
--- a/packages/SystemUI/res/drawable/corner_gesture_hint.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-    Copyright (C) 2019 The Android Open Source Project
-
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:height="12dp"
-    android:width="12dp"
-    android:viewportWidth="12"
-    android:viewportHeight="12">
-
-    <path android:fillColor="#00000000"
-          android:pathData="M 1.18 10.65 C 1.18 5.58 5.41 1.18 10.65 1.18"
-          android:strokeColor="#000"
-          android:strokeLineCap="round"
-          android:strokeWidth="1.3" />
-</vector>
diff --git a/packages/SystemUI/res/layout/rounded_corners.xml b/packages/SystemUI/res/layout/rounded_corners.xml
index 02651a2..b409c8f 100644
--- a/packages/SystemUI/res/layout/rounded_corners.xml
+++ b/packages/SystemUI/res/layout/rounded_corners.xml
@@ -18,36 +18,30 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
+    <com.android.systemui.CornerHandleView
+        android:id="@+id/assist_hint_left"
+        android:layout_width="36dp"
+        android:layout_height="36dp"
+        android:layout_gravity="left|top"
+        android:visibility="gone"/>
+    <com.android.systemui.CornerHandleView
+        android:id="@+id/assist_hint_right"
+        android:layout_width="36dp"
+        android:layout_height="36dp"
+        android:layout_gravity="right|bottom"
+        android:visibility="gone"/>
     <ImageView
         android:id="@+id/left"
         android:layout_width="12dp"
         android:layout_height="12dp"
         android:layout_gravity="left|top"
         android:tint="#ff000000"
-        android:src="@drawable/rounded" />
+        android:src="@drawable/rounded"/>
     <ImageView
         android:id="@+id/right"
         android:layout_width="12dp"
         android:layout_height="12dp"
         android:tint="#ff000000"
         android:layout_gravity="right|bottom"
-        android:src="@drawable/rounded" />
-    <ImageView
-        android:id="@+id/assist_hint_left"
-        android:layout_width="32dp"
-        android:layout_height="32dp"
-        android:padding="6dp"
-        android:layout_gravity="left|top"
-        android:src="@drawable/corner_gesture_hint"
-        android:tint="#ffffffff"
-        android:visibility="gone" />
-    <ImageView
-        android:id="@+id/assist_hint_right"
-        android:layout_width="32dp"
-        android:layout_height="32dp"
-        android:padding="6dp"
-        android:layout_gravity="right|bottom"
-        android:src="@drawable/corner_gesture_hint"
-        android:tint="#ffffffff"
-        android:visibility="gone" />
+        android:src="@drawable/rounded"/>
 </com.android.systemui.RegionInterceptingFrameLayout>
diff --git a/packages/SystemUI/src/com/android/systemui/CornerHandleView.java b/packages/SystemUI/src/com/android/systemui/CornerHandleView.java
new file mode 100644
index 0000000..528db5a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/CornerHandleView.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui;
+
+import android.animation.ArgbEvaluator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.os.SystemProperties;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.ContextThemeWrapper;
+import android.view.View;
+
+import com.android.settingslib.Utils;
+
+/**
+ * CornerHandleView draws an inset arc intended to be displayed within the screen decoration
+ * corners.
+ */
+public class CornerHandleView extends View {
+    private static final boolean ALLOW_TUNING = false;
+    private static final int ANGLE_DEGREES = 50;
+    public static final int MARGIN_DP = 11;
+    public static final int RADIUS_DP = 37;
+    public static final float STROKE_DP = 2.5f;
+
+    private Paint mPaint;
+    private int mLightColor;
+    private int mDarkColor;
+    private RectF mOval;
+
+
+    public CornerHandleView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        mPaint = new Paint();
+        mPaint.setAntiAlias(true);
+        mPaint.setStyle(Paint.Style.STROKE);
+        mPaint.setStrokeCap(Paint.Cap.ROUND);
+        mPaint.setStrokeWidth(getStrokePx());
+
+        final int dualToneDarkTheme = Utils.getThemeAttr(mContext,
+                R.attr.darkIconTheme);
+        final int dualToneLightTheme = Utils.getThemeAttr(mContext,
+                R.attr.lightIconTheme);
+        Context lightContext = new ContextThemeWrapper(mContext, dualToneLightTheme);
+        Context darkContext = new ContextThemeWrapper(mContext, dualToneDarkTheme);
+        mLightColor = Utils.getColorAttrDefaultColor(lightContext,
+                R.attr.singleToneColor);
+        mDarkColor = Utils.getColorAttrDefaultColor(darkContext,
+                R.attr.singleToneColor);
+
+        updateOval();
+    }
+
+    /**
+     * Receives an intensity from 0 (lightest) to 1 (darkest) and sets the handle color
+     * approriately. Intention is to match the home handle color.
+     */
+    public void updateDarkness(float darkIntensity) {
+        mPaint.setColor((int) ArgbEvaluator.getInstance().evaluate(darkIntensity,
+                mLightColor,
+                mDarkColor));
+        invalidate();
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (ALLOW_TUNING) {
+            mPaint.setStrokeWidth(getStrokePx());
+            updateOval();
+        }
+
+        canvas.drawArc(mOval, 180 + ((90 - getAngle()) / 2), getAngle(), false,
+                mPaint);
+    }
+
+    // TODO(b/133834204): Remove tweaking of corner handles
+    private static float convertDpToPixel(float dp, Context context) {
+        return dp * ((float) context.getResources().getDisplayMetrics().densityDpi
+                / DisplayMetrics.DENSITY_DEFAULT);
+    }
+
+    private void updateOval() {
+        mOval = new RectF(getMarginPx() - (getStrokePx() / 2.f),
+                getMarginPx() - (getStrokePx() / 2.f),
+                getMarginPx() + (2 * (getRadiusPx()) + (getStrokePx() / 2.f)),
+                getMarginPx() + 2 * getRadiusPx() + (getStrokePx() / 2.f));
+    }
+
+    private int getAngle() {
+        if (ALLOW_TUNING) {
+            return SystemProperties.getInt("CORNER_HANDLE_ANGLE_DEGREES", ANGLE_DEGREES);
+        } else {
+            return ANGLE_DEGREES;
+        }
+    }
+
+    private int getMarginPx() {
+        if (ALLOW_TUNING) {
+            return SystemProperties.getInt("CORNER_HANDLE_MARGIN_PX",
+                    (int) convertDpToPixel(MARGIN_DP, getContext()));
+        } else {
+            return (int) convertDpToPixel(MARGIN_DP, getContext());
+        }
+    }
+
+    private int getRadiusPx() {
+        if (ALLOW_TUNING) {
+            return SystemProperties.getInt("CORNER_HANDLE_RADIUS_PX",
+                    (int) convertDpToPixel(RADIUS_DP, getContext()));
+        } else {
+            return (int) convertDpToPixel(RADIUS_DP, getContext());
+        }
+    }
+
+    private int getStrokePx() {
+        if (ALLOW_TUNING) {
+            return SystemProperties.getInt("CORNER_HANDLE_STROKE_PX",
+                    (int) convertDpToPixel(STROKE_DP, getContext()));
+        } else {
+            return (int) convertDpToPixel(STROKE_DP, getContext());
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 390a9e6..6057e12 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -60,6 +60,12 @@
 import android.view.ViewGroup.LayoutParams;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.OvershootInterpolator;
+import android.view.animation.PathInterpolator;
+import android.view.animation.ScaleAnimation;
+import android.view.animation.TranslateAnimation;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
@@ -72,6 +78,7 @@
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.qs.SecureSetting;
 import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
+import com.android.systemui.statusbar.phone.NavigationBarTransitions;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.tuner.TunablePadding;
 import com.android.systemui.tuner.TunerService;
@@ -85,7 +92,8 @@
  * An overlay that draws screen decorations in software (e.g for rounded corners or display cutout)
  * for antialiasing and emulation purposes.
  */
-public class ScreenDecorations extends SystemUI implements Tunable {
+public class ScreenDecorations extends SystemUI implements Tunable,
+        NavigationBarTransitions.DarkIntensityListener {
     private static final boolean DEBUG = false;
     private static final String TAG = "ScreenDecorations";
 
@@ -93,13 +101,17 @@
     public static final String PADDING = "sysui_rounded_content_padding";
     private static final boolean DEBUG_SCREENSHOT_ROUNDED_CORNERS =
             SystemProperties.getBoolean("debug.screenshot_rounded_corners", false);
+    private static final boolean VERBOSE = false;
 
     private DisplayManager mDisplayManager;
     private DisplayManager.DisplayListener mDisplayListener;
 
-    @VisibleForTesting protected int mRoundedDefault;
-    @VisibleForTesting protected int mRoundedDefaultTop;
-    @VisibleForTesting protected int mRoundedDefaultBottom;
+    @VisibleForTesting
+    protected int mRoundedDefault;
+    @VisibleForTesting
+    protected int mRoundedDefaultTop;
+    @VisibleForTesting
+    protected int mRoundedDefaultBottom;
     private View mOverlay;
     private View mBottomOverlay;
     private float mDensity;
@@ -111,6 +123,8 @@
     private SecureSetting mColorInversionSetting;
     private boolean mPendingRotationChange;
     private Handler mHandler;
+    private boolean mAssistHintBlocked = false;
+    private boolean mIsReceivingNavBarColor = false;
 
     /**
      * Converts a set of {@link Rect}s into a {@link Region}
@@ -137,15 +151,32 @@
         putComponent(ScreenDecorations.class, this);
     }
 
-    private void fade(View view, boolean fadeIn) {
+    private void fade(View view, boolean fadeIn, boolean isLeft) {
         if (fadeIn) {
             view.animate().cancel();
-            view.setAlpha(0f);
+            view.setAlpha(1f);
             view.setVisibility(View.VISIBLE);
-            view.animate().alpha(1f);
+
+            AnimationSet anim = new AnimationSet(true);
+            anim.setDuration(900);
+
+            Animation scaleAnimation = new ScaleAnimation(2f, 1f, 2f, 1f,
+                    ScaleAnimation.RELATIVE_TO_SELF, 0.5f, ScaleAnimation.RELATIVE_TO_SELF, 0.5f);
+            anim.addAnimation(scaleAnimation);
+            anim.setInterpolator(new PathInterpolator(0.02f, 0.44f, 0.67f, 1.00f));
+
+            Animation translateAnimation = new TranslateAnimation(
+                    TranslateAnimation.RELATIVE_TO_SELF, isLeft ? -0.2f : 0.2f,
+                    TranslateAnimation.RELATIVE_TO_SELF,
+                    0f,
+                    TranslateAnimation.RELATIVE_TO_SELF, 0.2f, TranslateAnimation.RELATIVE_TO_SELF,
+                    0f);
+            anim.addAnimation(translateAnimation);
+            anim.setInterpolator(new OvershootInterpolator());
+            view.startAnimation(anim);
         } else {
             view.animate().cancel();
-            view.animate().alpha(0f).withEndAction(() -> view.setVisibility(View.INVISIBLE));
+            view.animate().setDuration(400).alpha(0f);
         }
 
     }
@@ -161,35 +192,59 @@
             return;
         }
 
+        if (mAssistHintBlocked && visible) {
+            if (VERBOSE) {
+                Log.v(TAG, "Assist hint blocked, cannot make it visible");
+            }
+            return;
+        }
+
         if (mAssistHintVisible != visible) {
             mAssistHintVisible = visible;
 
-            View assistHintTopLeft = mOverlay.findViewById(R.id.assist_hint_left);
-            View assistHintTopRight = mOverlay.findViewById(R.id.assist_hint_right);
-            View assistHintBottomLeft = mBottomOverlay.findViewById(R.id.assist_hint_left);
-            View assistHintBottomRight = mBottomOverlay.findViewById(R.id.assist_hint_right);
+            CornerHandleView assistHintTopLeft = mOverlay.findViewById(R.id.assist_hint_left);
+            CornerHandleView assistHintTopRight = mOverlay.findViewById(R.id.assist_hint_right);
+            CornerHandleView assistHintBottomLeft = mBottomOverlay.findViewById(
+                    R.id.assist_hint_left);
+            CornerHandleView assistHintBottomRight = mBottomOverlay.findViewById(
+                    R.id.assist_hint_right);
 
             switch (mRotation) {
                 case RotationUtils.ROTATION_NONE:
-                    fade(assistHintBottomLeft, mAssistHintVisible);
-                    fade(assistHintBottomRight, mAssistHintVisible);
+                    fade(assistHintBottomLeft, mAssistHintVisible, /* isLeft = */ true);
+                    fade(assistHintBottomRight, mAssistHintVisible, /* isLeft = */ false);
                     break;
                 case RotationUtils.ROTATION_LANDSCAPE:
-                    fade(assistHintTopRight, mAssistHintVisible);
-                    fade(assistHintBottomRight, mAssistHintVisible);
+                    fade(assistHintTopRight, mAssistHintVisible, /* isLeft = */ true);
+                    fade(assistHintBottomRight, mAssistHintVisible, /* isLeft = */ false);
                     break;
                 case RotationUtils.ROTATION_SEASCAPE:
-                    fade(assistHintTopLeft, mAssistHintVisible);
-                    fade(assistHintBottomLeft, mAssistHintVisible);
+                    fade(assistHintTopLeft, mAssistHintVisible, /* isLeft = */ false);
+                    fade(assistHintBottomLeft, mAssistHintVisible,  /* isLeft = */ true);
                     break;
                 case RotationUtils.ROTATION_UPSIDE_DOWN:
-                    fade(assistHintTopLeft, mAssistHintVisible);
-                    fade(assistHintTopRight, mAssistHintVisible);
+                    fade(assistHintTopLeft, mAssistHintVisible, /* isLeft = */ false);
+                    fade(assistHintTopRight, mAssistHintVisible, /* isLeft = */ true);
                     break;
             }
         }
     }
 
+    /**
+     * Prevents the assist hint from becoming visible even if `mAssistHintVisible` is true.
+     */
+    public void setAssistHintBlocked(boolean blocked) {
+        if (!mHandler.getLooper().isCurrentThread()) {
+            mHandler.post(() -> setAssistHintBlocked(blocked));
+            return;
+        }
+
+        mAssistHintBlocked = blocked;
+        if (mAssistHintVisible && mAssistHintBlocked) {
+            setAssistHintVisible(false);
+        }
+    }
+
     @VisibleForTesting
     Handler startHandlerThread() {
         HandlerThread thread = new HandlerThread("ScreenDecorations");
@@ -253,12 +308,12 @@
                 .inflate(R.layout.rounded_corners, null);
         mCutoutTop = new DisplayCutoutView(mContext, true,
                 this::updateWindowVisibilities, this);
-        ((ViewGroup)mOverlay).addView(mCutoutTop);
+        ((ViewGroup) mOverlay).addView(mCutoutTop);
         mBottomOverlay = LayoutInflater.from(mContext)
                 .inflate(R.layout.rounded_corners, null);
         mCutoutBottom = new DisplayCutoutView(mContext, false,
                 this::updateWindowVisibilities, this);
-        ((ViewGroup)mBottomOverlay).addView(mCutoutBottom);
+        ((ViewGroup) mBottomOverlay).addView(mCutoutBottom);
 
         mOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
         mOverlay.setAlpha(0);
@@ -416,7 +471,7 @@
         } else if (mRotation == RotationUtils.ROTATION_LANDSCAPE) {
             updateView(topLeft, Gravity.TOP | Gravity.LEFT, 0);
             updateView(topRight, Gravity.BOTTOM | Gravity.LEFT, 270);
-            updateView(bottomLeft, Gravity.TOP | Gravity.RIGHT, 90);;
+            updateView(bottomLeft, Gravity.TOP | Gravity.RIGHT, 90);
             updateView(bottomRight, Gravity.BOTTOM | Gravity.RIGHT, 180);
         } else if (mRotation == RotationUtils.ROTATION_UPSIDE_DOWN) {
             updateView(topLeft, Gravity.BOTTOM | Gravity.LEFT, 270);
@@ -477,7 +532,7 @@
     }
 
     private void updateView(View v, int gravity, int rotation) {
-        ((FrameLayout.LayoutParams)v.getLayoutParams()).gravity = gravity;
+        ((FrameLayout.LayoutParams) v.getLayoutParams()).gravity = gravity;
         v.setRotation(rotation);
     }
 
@@ -613,6 +668,10 @@
                 setSize(mOverlay.findViewById(R.id.right), sizeTop);
                 setSize(mBottomOverlay.findViewById(R.id.left), sizeBottom);
                 setSize(mBottomOverlay.findViewById(R.id.right), sizeBottom);
+                setSize(mOverlay.findViewById(R.id.assist_hint_left), sizeTop * 2);
+                setSize(mOverlay.findViewById(R.id.assist_hint_right), sizeTop * 2);
+                setSize(mBottomOverlay.findViewById(R.id.assist_hint_left), sizeBottom * 2);
+                setSize(mBottomOverlay.findViewById(R.id.assist_hint_right), sizeBottom * 2);
             }
         });
     }
@@ -624,6 +683,27 @@
         view.setLayoutParams(params);
     }
 
+    @Override
+    public void onDarkIntensity(float darkIntensity) {
+        if (mOverlay != null) {
+            CornerHandleView assistHintTopLeft = mOverlay.findViewById(R.id.assist_hint_left);
+            CornerHandleView assistHintTopRight = mOverlay.findViewById(R.id.assist_hint_right);
+
+            assistHintTopLeft.updateDarkness(darkIntensity);
+            assistHintTopRight.updateDarkness(darkIntensity);
+        }
+
+        if (mBottomOverlay != null) {
+            CornerHandleView assistHintBottomLeft = mBottomOverlay.findViewById(
+                    R.id.assist_hint_left);
+            CornerHandleView assistHintBottomRight = mBottomOverlay.findViewById(
+                    R.id.assist_hint_right);
+
+            assistHintBottomLeft.updateDarkness(darkIntensity);
+            assistHintBottomRight.updateDarkness(darkIntensity);
+        }
+    }
+
     @VisibleForTesting
     static class TunablePaddingTagListener implements FragmentListener {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 337c6b1..d94a335 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -86,6 +86,7 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.ScreenDecorations;
 import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.fragments.FragmentHostManager;
@@ -170,6 +171,7 @@
     public int mDisplayId;
     private boolean mIsOnDefaultDisplay;
     public boolean mHomeBlockedThisTouch;
+    private ScreenDecorations mScreenDecorations;
 
     private Handler mHandler = Dependency.get(Dependency.MAIN_HANDLER);
 
@@ -348,12 +350,17 @@
             mDisabledFlags2 |= StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS;
         }
         setDisabled2Flags(mDisabledFlags2);
+
+        mScreenDecorations = SysUiServiceProvider.getComponent(getContext(),
+                ScreenDecorations.class);
+        getBarTransitions().addDarkIntensityListener(mScreenDecorations);
     }
 
     @Override
     public void onDestroyView() {
         super.onDestroyView();
         if (mNavigationBarView != null) {
+            mNavigationBarView.getBarTransitions().removeDarkIntensityListener(mScreenDecorations);
             mNavigationBarView.getBarTransitions().destroy();
             mNavigationBarView.getLightTransitionsController().destroy(getContext());
         }
@@ -1020,7 +1027,7 @@
         getBarTransitions().transitionTo(barMode, animate);
     }
 
-    private BarTransitions getBarTransitions() {
+    public NavigationBarTransitions getBarTransitions() {
         return mNavigationBarView.getBarTransitions();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
index 8a28c6f..2b5a28e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -36,9 +36,23 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 
+import java.util.ArrayList;
+import java.util.List;
+
 public final class NavigationBarTransitions extends BarTransitions implements
         LightBarTransitionsController.DarkIntensityApplier {
 
+    /**
+     * Notified when the color of nav bar elements changes.
+     */
+    public interface DarkIntensityListener {
+        /**
+         * Called when the color of nav bar elements changes.
+         * @param darkIntensity 0 is the lightest color, 1 is the darkest.
+         */
+        void onDarkIntensity(float darkIntensity);
+    }
+
     private final NavigationBarView mView;
     private final IStatusBarService mBarService;
     private final LightBarTransitionsController mLightTransitionsController;
@@ -49,6 +63,7 @@
     private boolean mAutoDim;
     private View mNavButtons;
     private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
+    private List<DarkIntensityListener> mDarkIntensityListeners;
 
     private final Handler mHandler = Handler.getMain();
     private final IWallpaperVisibilityListener mWallpaperVisibilityListener =
@@ -69,6 +84,7 @@
         mLightTransitionsController = new LightBarTransitionsController(view.getContext(), this);
         mAllowAutoDimWallpaperNotVisible = view.getContext().getResources()
                 .getBoolean(R.bool.config_navigation_bar_enable_auto_dim_no_visible_wallpaper);
+        mDarkIntensityListeners = new ArrayList();
 
         IWindowManager windowManagerService = Dependency.get(IWindowManager.class);
         try {
@@ -168,12 +184,16 @@
         applyDarkIntensity(mLightTransitionsController.getCurrentDarkIntensity());
     }
 
+    @Override
     public void applyDarkIntensity(float darkIntensity) {
         SparseArray<ButtonDispatcher> buttonDispatchers = mView.getButtonDispatchers();
         for (int i = buttonDispatchers.size() - 1; i >= 0; i--) {
             buttonDispatchers.valueAt(i).setDarkIntensity(darkIntensity);
         }
         mView.getRotationButtonController().setDarkIntensity(darkIntensity);
+        for (DarkIntensityListener listener : mDarkIntensityListeners) {
+            listener.onDarkIntensity(darkIntensity);
+        }
         if (mAutoDim) {
             applyLightsOut(false, true);
         }
@@ -190,4 +210,18 @@
     public void onNavigationModeChanged(int mode) {
         mNavBarMode = mode;
     }
+
+    /**
+     * Register {@code listener} to be notified when the color of nav bar elements changes.
+     */
+    public void addDarkIntensityListener(DarkIntensityListener listener) {
+        mDarkIntensityListeners.add(listener);
+    }
+
+    /**
+     * Remove {@code listener} from being notified when the color of nav bar elements changes.
+     */
+    public void removeDarkIntensityListener(DarkIntensityListener listener) {
+        mDarkIntensityListeners.remove(listener);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 7682e8a..8a895e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -299,7 +299,7 @@
         return mTintController;
     }
 
-    public BarTransitions getBarTransitions() {
+    public NavigationBarTransitions getBarTransitions() {
         return mBarTransitions;
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
index 4181d38..faf5a97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
@@ -268,7 +268,7 @@
             when(view.getRecentsButton()).thenReturn(mock(ButtonDispatcher.class));
             when(view.getAccessibilityButton()).thenReturn(mock(ButtonDispatcher.class));
             when(view.getRotateSuggestionButton()).thenReturn(mock(RotationContextButton.class));
-            when(view.getBarTransitions()).thenReturn(mock(BarTransitions.class));
+            when(view.getBarTransitions()).thenReturn(mock(NavigationBarTransitions.class));
             when(view.getLightTransitionsController()).thenReturn(
                     mock(LightBarTransitionsController.class));
             when(view.getRotationButtonController()).thenReturn(