diff options
7 files changed, 272 insertions, 5 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java index e0c5516417ba..1e00da5ad962 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java @@ -76,11 +76,14 @@ public class LightBarController implements BatteryController.BatteryStateChangeC private final Rect mLastDockedBounds = new Rect(); private boolean mQsCustomizing; + private final Context mContext; + public LightBarController(Context ctx) { mDarkModeColor = Color.valueOf(ctx.getColor(R.color.dark_mode_icon_color_single_tone)); mStatusBarIconController = Dependency.get(DarkIconDispatcher.class); mBatteryController = Dependency.get(BatteryController.class); mBatteryController.addCallback(this); + mContext = ctx; } public void setNavigationBar(LightBarTransitionsController navigationBar) { @@ -217,8 +220,9 @@ public class LightBarController implements BatteryController.BatteryStateChangeC private void updateNavigation() { if (mNavigationBarController != null) { - mNavigationBarController.setIconsDark( - mNavigationLight, animateChange()); + if (!NavBarTintController.isEnabled(mContext)) { + mNavigationBarController.setIconsDark(mNavigationLight, animateChange()); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java index 57cc7d6c1ecb..7876aa5d89d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java @@ -16,12 +16,16 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.statusbar.phone.NavBarTintController.MIN_COLOR_ADAPT_TRANSITION_TIME; +import static com.android.systemui.statusbar.phone.NavBarTintController.NAV_COLOR_TRANSITION_TIME_SETTING; + import android.animation.ValueAnimator; import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.SystemClock; import android.util.MathUtils; +import android.provider.Settings; import android.util.TimeUtils; import com.android.systemui.Dependency; @@ -42,13 +46,14 @@ import java.io.PrintWriter; public class LightBarTransitionsController implements Dumpable, Callbacks, StatusBarStateController.StateListener { - public static final long DEFAULT_TINT_ANIMATION_DURATION = 120; + public static final int DEFAULT_TINT_ANIMATION_DURATION = 120; private static final String EXTRA_DARK_INTENSITY = "dark_intensity"; private final Handler mHandler; private final DarkIntensityApplier mApplier; private final KeyguardMonitor mKeyguardMonitor; private final StatusBarStateController mStatusBarStateController; + private NavBarTintController mColorAdaptionController; private boolean mTransitionDeferring; private long mTransitionDeferringStartTime; @@ -67,6 +72,8 @@ public class LightBarTransitionsController implements Dumpable, Callbacks, } }; + private final Context mContext; + public LightBarTransitionsController(Context context, DarkIntensityApplier applier) { mApplier = applier; mHandler = new Handler(); @@ -76,6 +83,7 @@ public class LightBarTransitionsController implements Dumpable, Callbacks, .addCallback(this); mStatusBarStateController.addCallback(this); mDozeAmount = mStatusBarStateController.getDozeAmount(); + mContext = context; } public void destroy(Context context) { @@ -106,7 +114,7 @@ public class LightBarTransitionsController implements Dumpable, Callbacks, public void appTransitionCancelled() { if (mTransitionPending && mTintChangePending) { mTintChangePending = false; - animateIconTint(mPendingDarkIntensity, 0 /* delay */, DEFAULT_TINT_ANIMATION_DURATION); + animateIconTint(mPendingDarkIntensity, 0 /* delay */, getTintAnimationDuration()); } mTransitionPending = false; } @@ -146,8 +154,17 @@ public class LightBarTransitionsController implements Dumpable, Callbacks, Math.max(0, mTransitionDeferringStartTime - SystemClock.uptimeMillis()), mTransitionDeferringDuration); } else { - animateIconTint(dark ? 1.0f : 0.0f, 0 /* delay */, DEFAULT_TINT_ANIMATION_DURATION); + animateIconTint(dark ? 1.0f : 0.0f, 0 /* delay */, getTintAnimationDuration()); + } + } + + public long getTintAnimationDuration() { + if (NavBarTintController.isEnabled(mContext)) { + return Math.max(Settings.Global.getInt(mContext.getContentResolver(), + NAV_COLOR_TRANSITION_TIME_SETTING, DEFAULT_TINT_ANIMATION_DURATION), + MIN_COLOR_ADAPT_TRANSITION_TIME); } + return DEFAULT_TINT_ANIMATION_DURATION; } public float getCurrentDarkIntensity() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavBarTintController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavBarTintController.java new file mode 100644 index 000000000000..9ecee1825f07 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavBarTintController.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2018 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.statusbar.phone; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Color; +import android.graphics.Rect; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.provider.Settings; +import android.util.DisplayMetrics; +import android.view.SurfaceControl; + +public class NavBarTintController { + public static final String NAV_COLOR_TRANSITION_TIME_SETTING = "navbar_color_adapt_transition"; + public static final int MIN_COLOR_ADAPT_TRANSITION_TIME = 400; + + private final HandlerThread mColorAdaptHandlerThread = new HandlerThread("ColorExtractThread"); + private Handler mColorAdaptionHandler; + + // Poll time for each iteration to color sample + private static final int COLOR_ADAPTION_TIMEOUT = 300; + + // Passing the threshold of this luminance value will make the button black otherwise white + private static final float LUMINANCE_THRESHOLD = 0.3f; + + // The home button's icon is actually smaller than the button's size, the percentage will + // cut into the button's size to determine the icon size + private static final float PERCENTAGE_BUTTON_PADDING = 0.3f; + + // The distance from the home button to color sample around + private static final int COLOR_SAMPLE_MARGIN = 20; + + private boolean mRunning; + + private final NavigationBarView mNavigationBarView; + private final LightBarTransitionsController mLightBarController; + private final Handler mMainHandler = new Handler(Looper.getMainLooper()); + + public NavBarTintController(NavigationBarView navigationBarView, + LightBarTransitionsController lightBarController) { + mNavigationBarView = navigationBarView; + mLightBarController = lightBarController; + } + + public void start() { + if (!isEnabled(mNavigationBarView.getContext())) { + return; + } + if (mColorAdaptionHandler == null) { + mColorAdaptHandlerThread.start(); + mColorAdaptionHandler = new Handler(mColorAdaptHandlerThread.getLooper()); + } + mColorAdaptionHandler.removeCallbacksAndMessages(null); + mColorAdaptionHandler.post(this::updateTint); + mRunning = true; + } + + public void end() { + if (mColorAdaptionHandler != null) { + mColorAdaptionHandler.removeCallbacksAndMessages(null); + } + mRunning = false; + } + + public void stop() { + end(); + if (mColorAdaptionHandler != null) { + mColorAdaptHandlerThread.quitSafely(); + } + } + + private void updateTint() { + int[] navPos = new int[2]; + int[] butPos = new int[2]; + if (mNavigationBarView.getHomeButton().getCurrentView() == null) { + return; + } + + // Determine the area of the home icon in the larger home button + mNavigationBarView.getHomeButton().getCurrentView().getLocationInSurface(butPos); + final int navWidth = mNavigationBarView.getHomeButton().getCurrentView().getWidth(); + final int navHeight = mNavigationBarView.getHomeButton().getCurrentView().getHeight(); + final int xPadding = (int) (PERCENTAGE_BUTTON_PADDING * navWidth); + final int yPadding = (int) (PERCENTAGE_BUTTON_PADDING * navHeight); + final Rect homeButtonRect = new Rect(butPos[0] + xPadding, butPos[1] + yPadding, + navWidth + butPos[0] - xPadding, navHeight + butPos[1] - yPadding); + if (mNavigationBarView.getCurrentView() == null || homeButtonRect.isEmpty()) { + scheduleColorAdaption(); + return; + } + mNavigationBarView.getCurrentView().getLocationOnScreen(navPos); + homeButtonRect.offset(navPos[0], navPos[1]); + + // Apply a margin area around the button region to sample the colors, crop from screenshot + final Rect cropRect = new Rect(homeButtonRect); + cropRect.inset(-COLOR_SAMPLE_MARGIN, -COLOR_SAMPLE_MARGIN); + if (cropRect.isEmpty()) { + scheduleColorAdaption(); + return; + } + + // Determine the size of the home area + Rect homeArea = new Rect(COLOR_SAMPLE_MARGIN, COLOR_SAMPLE_MARGIN, + homeButtonRect.width() + COLOR_SAMPLE_MARGIN, + homeButtonRect.height() + COLOR_SAMPLE_MARGIN); + + // Get the screenshot around the home button icon to determine the color + DisplayMetrics mDisplayMetrics = new DisplayMetrics(); + mNavigationBarView.getContext().getDisplay().getRealMetrics(mDisplayMetrics); + final Bitmap hardBitmap = SurfaceControl + .screenshot(new Rect(), mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels, + mNavigationBarView.getContext().getDisplay().getRotation()); + if (hardBitmap != null && cropRect.bottom <= hardBitmap.getHeight()) { + final Bitmap cropBitmap = Bitmap.createBitmap(hardBitmap, cropRect.left, cropRect.top, + cropRect.width(), cropRect.height()); + final Bitmap softBitmap = cropBitmap.copy(Config.ARGB_8888, false); + + // Get the luminance value to determine if the home button should be black or white + final int[] pixels = new int[softBitmap.getByteCount() / 4]; + softBitmap.getPixels(pixels, 0, softBitmap.getWidth(), 0, 0, softBitmap.getWidth(), + softBitmap.getHeight()); + float r = 0, g = 0, blue = 0; + + int width = cropRect.width(); + int total = 0; + for (int i = 0; i < pixels.length; i += 4) { + int x = i % width; + int y = i / width; + if (!homeArea.contains(x, y)) { + r += Color.red(pixels[i]); + g += Color.green(pixels[i]); + blue += Color.blue(pixels[i]); + total++; + } + } + + r /= total; + g /= total; + blue /= total; + + r = Math.max(Math.min(r / 255f, 1), 0); + g = Math.max(Math.min(g / 255f, 1), 0); + blue = Math.max(Math.min(blue / 255f, 1), 0); + + if (r <= 0.03928) { + r /= 12.92; + } else { + r = (float) Math.pow((r + 0.055) / 1.055, 2.4); + } + if (g <= 0.03928) { + g /= 12.92; + } else { + g = (float) Math.pow((g + 0.055) / 1.055, 2.4); + } + if (blue <= 0.03928) { + blue /= 12.92; + } else { + blue = (float) Math.pow((blue + 0.055) / 1.055, 2.4); + } + + if (r * 0.2126 + g * 0.7152 + blue * 0.0722 > LUMINANCE_THRESHOLD) { + // Black + mMainHandler.post( + () -> mLightBarController + .setIconsDark(true /* dark */, true /* animate */)); + } else { + // White + mMainHandler.post( + () -> mLightBarController + .setIconsDark(false /* dark */, true /* animate */)); + } + cropBitmap.recycle(); + hardBitmap.recycle(); + } + scheduleColorAdaption(); + } + + private void scheduleColorAdaption() { + mColorAdaptionHandler.removeCallbacksAndMessages(null); + if (!mRunning || !isEnabled(mNavigationBarView.getContext())) { + return; + } + mColorAdaptionHandler.postDelayed(this::updateTint, COLOR_ADAPTION_TIMEOUT); + } + + public static boolean isEnabled(Context context) { + return Settings.Global.getInt(context.getContentResolver(), + NavigationPrototypeController.NAV_COLOR_ADAPT_ENABLE_SETTING, 0) == 1; + } +} 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 ae0a1452905d..55655d5b6240 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -851,6 +851,16 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback if (Intent.ACTION_SCREEN_OFF.equals(action) || Intent.ACTION_SCREEN_ON.equals(action)) { notifyNavigationBarScreenOn(); + + if (Intent.ACTION_SCREEN_ON.equals(action)) { + // Enabled and screen is on, start it again if enabled + if (NavBarTintController.isEnabled(getContext())) { + mNavigationBarView.getColorAdaptionController().start(); + } + } else { + // Screen off disable it + mNavigationBarView.getColorAdaptionController().end(); + } } if (Intent.ACTION_USER_SWITCHED.equals(action)) { // The accessibility settings may be different for the new user 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 30e840926698..6a7983af862d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -149,6 +149,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav private RecentsOnboarding mRecentsOnboarding; private NotificationPanelView mPanelView; + private NavBarTintController mColorAdaptionController; private NavigationPrototypeController mPrototypeController; private NavigationGestureAction[] mDefaultGestureMap; private QuickScrubAction mQuickScrubAction; @@ -277,6 +278,15 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav public void onBackButtonVisibilityChanged(boolean visible) { getBackButton().setVisibility(visible ? VISIBLE : GONE); } + + @Override + public void onColorAdaptChanged(boolean enabled) { + if (enabled) { + mColorAdaptionController.start(); + } else { + mColorAdaptionController.end(); + } + } }; public NavigationBarView(Context context, AttributeSet attrs) { @@ -334,6 +344,11 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav mPrototypeController = new NavigationPrototypeController(mHandler, mContext); mPrototypeController.register(); mPrototypeController.setOnPrototypeChangedListener(mPrototypeListener); + mColorAdaptionController = new NavBarTintController(this, getLightTransitionsController()); + } + + public NavBarTintController getColorAdaptionController() { + return mColorAdaptionController; } public BarTransitions getBarTransitions() { @@ -1097,6 +1112,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav Dependency.get(PluginManager.class).addPluginListener(this, NavGesture.class, false /* Only one */); setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled()); + mColorAdaptionController.start(); } @Override @@ -1107,6 +1123,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav mGestureHelper.destroy(); } mPrototypeController.unregister(); + mColorAdaptionController.stop(); setUpSwipeUpOnboarding(false); for (int i = 0; i < mButtonDispatchers.size(); ++i) { mButtonDispatchers.valueAt(i).onDestroy(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java index b11b6d472713..40ac79376b06 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java @@ -37,6 +37,7 @@ public class NavigationPrototypeController extends ContentObserver { static final String NAVBAR_EXPERIMENTS_DISABLED = "navbarexperiments_disabled"; private final String GESTURE_MATCH_SETTING = "quickstepcontroller_gesture_match_map"; + public static final String NAV_COLOR_ADAPT_ENABLE_SETTING = "navbar_color_adapt_enable"; @Retention(RetentionPolicy.SOURCE) @IntDef({ACTION_DEFAULT, ACTION_QUICKSTEP, ACTION_QUICKSCRUB, ACTION_BACK}) @@ -73,6 +74,7 @@ public class NavigationPrototypeController extends ContentObserver { public void register() { registerObserver(HIDE_BACK_BUTTON_SETTING); registerObserver(GESTURE_MATCH_SETTING); + registerObserver(NAV_COLOR_ADAPT_ENABLE_SETTING); } /** @@ -96,6 +98,9 @@ public class NavigationPrototypeController extends ContentObserver { } else if (path.endsWith(HIDE_BACK_BUTTON_SETTING)) { mListener.onBackButtonVisibilityChanged( !getGlobalBool(HIDE_BACK_BUTTON_SETTING)); + } else if (path.endsWith(NAV_COLOR_ADAPT_ENABLE_SETTING)) { + mListener.onColorAdaptChanged( + NavBarTintController.isEnabled(mContext)); } } catch (SettingNotFoundException e) { e.printStackTrace(); @@ -138,5 +143,6 @@ public class NavigationPrototypeController extends ContentObserver { public interface OnPrototypeChangedListener { void onGestureRemap(@GestureAction int[] actions); void onBackButtonVisibilityChanged(boolean visible); + void onColorAdaptChanged(boolean enabled); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 1d6a1e81246c..78249ec44b33 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -2225,6 +2225,11 @@ public class StatusBar extends SystemUI implements DemoMode, mNavigationBar.getBarTransitions().setAutoDim(false); } mHandler.removeCallbacks(mAutoDim); + + // Do not dim the navigation buttons if the its tint is controlled by the bar's background + if (NavBarTintController.isEnabled(mContext)) { + return; + } if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED) { mHandler.postDelayed(mAutoDim, AUTOHIDE_TIMEOUT_MS); } |