/* * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.platform.internal.display; import android.animation.FloatArrayEvaluator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.content.Context; import android.net.Uri; import android.os.Handler; import android.os.IBinder; import android.os.Parcel; import android.os.RemoteException; import android.os.ServiceManager; import android.util.MathUtils; import android.util.Slog; import android.view.animation.LinearInterpolator; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import cyanogenmod.hardware.CMHardwareManager; import cyanogenmod.hardware.LiveDisplayManager; import cyanogenmod.providers.CMSettings; public class DisplayHardwareController extends LiveDisplayFeature { private final CMHardwareManager mHardware; // hardware capabilities private final boolean mUseAutoContrast; private final boolean mUseColorAdjustment; private final boolean mUseColorEnhancement; private final boolean mUseCABC; private final boolean mUseDisplayModes; // default values private final boolean mDefaultAutoContrast; private final boolean mDefaultColorEnhancement; private final boolean mDefaultCABC; // color adjustment holders private final float[] mAdditionalAdjustment = getDefaultAdjustment(); private final float[] mColorAdjustment = getDefaultAdjustment(); private ValueAnimator mAnimator; private final int mMaxColor; // settings uris private static final Uri DISPLAY_AUTO_CONTRAST = CMSettings.System.getUriFor(CMSettings.System.DISPLAY_AUTO_CONTRAST); private static final Uri DISPLAY_COLOR_ADJUSTMENT = CMSettings.System.getUriFor(CMSettings.System.DISPLAY_COLOR_ADJUSTMENT); private static final Uri DISPLAY_COLOR_ENHANCE = CMSettings.System.getUriFor(CMSettings.System.DISPLAY_COLOR_ENHANCE); private static final Uri DISPLAY_CABC = CMSettings.System.getUriFor(CMSettings.System.DISPLAY_CABC); public DisplayHardwareController(Context context, Handler handler) { super(context, handler); mHardware = CMHardwareManager.getInstance(mContext); mUseCABC = mHardware .isSupported(CMHardwareManager.FEATURE_ADAPTIVE_BACKLIGHT); mDefaultCABC = mContext.getResources().getBoolean( org.cyanogenmod.platform.internal.R.bool.config_defaultCABC); mUseColorEnhancement = mHardware .isSupported(CMHardwareManager.FEATURE_COLOR_ENHANCEMENT); mDefaultColorEnhancement = mContext.getResources().getBoolean( org.cyanogenmod.platform.internal.R.bool.config_defaultColorEnhancement); mUseAutoContrast = mHardware .isSupported(CMHardwareManager.FEATURE_AUTO_CONTRAST); mDefaultAutoContrast = mContext.getResources().getBoolean( org.cyanogenmod.platform.internal.R.bool.config_defaultAutoContrast); mUseColorAdjustment = mHardware .isSupported(CMHardwareManager.FEATURE_DISPLAY_COLOR_CALIBRATION); mUseDisplayModes = mHardware .isSupported(CMHardwareManager.FEATURE_DISPLAY_MODES); if (mUseColorAdjustment) { mMaxColor = mHardware.getDisplayColorCalibrationMax(); copyColors(getColorAdjustment(), mColorAdjustment); } else { mMaxColor = 0; } } @Override public void onStart() { final ArrayList settings = new ArrayList(); if (mUseCABC) { settings.add(DISPLAY_CABC); } if (mUseColorEnhancement) { settings.add(DISPLAY_COLOR_ENHANCE); } if (mUseAutoContrast) { settings.add(DISPLAY_AUTO_CONTRAST); } if (mUseColorAdjustment) { settings.add(DISPLAY_COLOR_ADJUSTMENT); } if (settings.size() == 0) { return; } registerSettings(settings.toArray(new Uri[settings.size()])); } @Override public boolean getCapabilities(final BitSet caps) { if (mUseAutoContrast) { caps.set(LiveDisplayManager.FEATURE_AUTO_CONTRAST); } if (mUseColorEnhancement) { caps.set(LiveDisplayManager.FEATURE_COLOR_ENHANCEMENT); } if (mUseCABC) { caps.set(LiveDisplayManager.FEATURE_CABC); } if (mUseColorAdjustment) { caps.set(LiveDisplayManager.FEATURE_COLOR_ADJUSTMENT); } if (mUseDisplayModes) { caps.set(LiveDisplayManager.FEATURE_DISPLAY_MODES); } return mUseAutoContrast || mUseColorEnhancement || mUseCABC || mUseColorAdjustment || mUseDisplayModes; } @Override public synchronized void onSettingsChanged(Uri uri) { if (uri == null || uri.equals(DISPLAY_CABC)) { updateCABCMode(); } if (uri == null || uri.equals(DISPLAY_AUTO_CONTRAST)) { updateAutoContrast(); } if (uri == null || uri.equals(DISPLAY_COLOR_ENHANCE)) { updateColorEnhancement(); } if (uri == null || uri.equals(DISPLAY_COLOR_ADJUSTMENT)) { copyColors(getColorAdjustment(), mColorAdjustment); updateColorAdjustment(); } } private synchronized void updateHardware() { if (isScreenOn()) { updateCABCMode(); updateAutoContrast(); updateColorEnhancement(); } } @Override protected void onUpdate() { updateHardware(); } @Override protected synchronized void onScreenStateChanged() { if (mUseColorAdjustment) { if (mAnimator != null && mAnimator.isRunning() && !isScreenOn()) { mAnimator.cancel(); } else if (isScreenOn()) { updateColorAdjustment(); } } } @Override public void dump(PrintWriter pw) { pw.println(); pw.println("DisplayHardwareController Configuration:"); pw.println(" mUseAutoContrast=" + mUseAutoContrast); pw.println(" mUseColorAdjustment=" + mUseColorAdjustment); pw.println(" mUseColorEnhancement=" + mUseColorEnhancement); pw.println(" mUseCABC=" + mUseCABC); pw.println(" mUseDisplayModes=" + mUseDisplayModes); pw.println(); pw.println(" DisplayHardwareController State:"); pw.println(" mAutoContrast=" + isAutoContrastEnabled()); pw.println(" mColorEnhancement=" + isColorEnhancementEnabled()); pw.println(" mCABC=" + isCABCEnabled()); pw.println(" mColorAdjustment=" + Arrays.toString(mColorAdjustment)); pw.println(" mAdditionalAdjustment=" + Arrays.toString(mAdditionalAdjustment)); pw.println(" hardware setting=" + Arrays.toString(mHardware.getDisplayColorCalibration())); } /** * Automatic contrast optimization */ private void updateAutoContrast() { if (!mUseAutoContrast) { return; } mHardware.set(CMHardwareManager.FEATURE_AUTO_CONTRAST, !isLowPowerMode() && isAutoContrastEnabled()); } /** * Color enhancement is optional */ private void updateColorEnhancement() { if (!mUseColorEnhancement) { return; } mHardware.set(CMHardwareManager.FEATURE_COLOR_ENHANCEMENT, !isLowPowerMode() && isColorEnhancementEnabled()); } /** * Adaptive backlight / low power mode. Turn it off when under very bright light. */ private void updateCABCMode() { if (!mUseCABC) { return; } mHardware.set(CMHardwareManager.FEATURE_ADAPTIVE_BACKLIGHT, !isLowPowerMode() && isCABCEnabled()); } private synchronized void updateColorAdjustment() { if (!mUseColorAdjustment) { return; } final float[] rgb = getDefaultAdjustment(); if (!isLowPowerMode()) { copyColors(mColorAdjustment, rgb); rgb[0] *= mAdditionalAdjustment[0]; rgb[1] *= mAdditionalAdjustment[1]; rgb[2] *= mAdditionalAdjustment[2]; } if (DEBUG) { Slog.d(TAG, "updateColorAdjustment: " + Arrays.toString(rgb)); } if (validateColors(rgb)) { animateDisplayColor(rgb); } } /** * Smoothly animate the current display colors to the new value. */ private synchronized void animateDisplayColor(float[] targetColors) { // always start with the current values in the hardware int[] currentInts = mHardware.getDisplayColorCalibration(); float[] currentColors = new float[] { (float)currentInts[0] / (float)mMaxColor, (float)currentInts[1] / (float)mMaxColor, (float)currentInts[2] / (float)mMaxColor }; if (currentColors[0] == targetColors[0] && currentColors[1] == targetColors[1] && currentColors[2] == targetColors[2]) { return; } // max 500 ms, scaled vs. the largest delta long duration = (long)(750 * (Math.max(Math.max( Math.abs(currentColors[0] - targetColors[0]), Math.abs(currentColors[1] - targetColors[1])), Math.abs(currentColors[2] - targetColors[2])))); if (DEBUG) { Slog.d(TAG, "animateDisplayColor current=" + Arrays.toString(currentColors) + " targetColors=" + Arrays.toString(targetColors) + " duration=" + duration); } if (mAnimator != null) { mAnimator.cancel(); mAnimator.removeAllUpdateListeners(); } mAnimator = ValueAnimator.ofObject( new FloatArrayEvaluator(new float[3]), currentColors, targetColors); mAnimator.setDuration(duration); mAnimator.setInterpolator(new LinearInterpolator()); mAnimator.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(final ValueAnimator animation) { synchronized (DisplayHardwareController.this) { if (isScreenOn()) { float[] value = (float[]) animation.getAnimatedValue(); mHardware.setDisplayColorCalibration(new int[] { (int) (value[0] * mMaxColor), (int) (value[1] * mMaxColor), (int) (value[2] * mMaxColor) }); screenRefresh(); } } } }); mAnimator.start(); } /** * Tell SurfaceFlinger to repaint the screen. This is called after updating * hardware registers for display calibration to have an immediate effect. */ private void screenRefresh() { try { final IBinder flinger = ServiceManager.getService("SurfaceFlinger"); if (flinger != null) { final Parcel data = Parcel.obtain(); data.writeInterfaceToken("android.ui.ISurfaceComposer"); flinger.transact(1004, data, null, 0); data.recycle(); } } catch (RemoteException ex) { Slog.e(TAG, "Failed to refresh screen", ex); } } /** * Ensure all values are within range * * @param colors * @return true if valid */ private boolean validateColors(float[] colors) { if (colors == null || colors.length != 3) { return false; } for (int i = 0; i < 3; i++) { colors[i] = MathUtils.constrain(colors[i], 0.0f, 1.0f); } return true; } /** * Parse and sanity check an RGB triplet from a string. */ private boolean parseColorAdjustment(String rgbString, float[] dest) { String[] adj = rgbString == null ? null : rgbString.split(" "); if (adj == null || adj.length != 3 || dest == null || dest.length != 3) { return false; } try { dest[0] = Float.parseFloat(adj[0]); dest[1] = Float.parseFloat(adj[1]); dest[2] = Float.parseFloat(adj[2]); } catch (NumberFormatException e) { Slog.e(TAG, e.getMessage(), e); return false; } // sanity check return validateColors(dest); } /** * Additional adjustments provided by night mode * * @param adj */ synchronized boolean setAdditionalAdjustment(float[] adj) { if (!mUseColorAdjustment) { return false; } if (DEBUG) { Slog.d(TAG, "setAdditionalAdjustment: " + Arrays.toString(adj)); } // Sanity check this so we don't mangle the display if (validateColors(adj)) { copyColors(adj, mAdditionalAdjustment); updateColorAdjustment(); return true; } return false; } boolean getDefaultCABC() { return mDefaultCABC; } boolean getDefaultAutoContrast() { return mDefaultAutoContrast; } boolean getDefaultColorEnhancement() { return mDefaultColorEnhancement; } boolean isAutoContrastEnabled() { return mUseAutoContrast && getBoolean(CMSettings.System.DISPLAY_AUTO_CONTRAST, mDefaultAutoContrast); } boolean setAutoContrastEnabled(boolean enabled) { if (!mUseAutoContrast) { return false; } putBoolean(CMSettings.System.DISPLAY_AUTO_CONTRAST, enabled); return true; } boolean isCABCEnabled() { return mUseCABC && getBoolean(CMSettings.System.DISPLAY_CABC, mDefaultCABC); } boolean setCABCEnabled(boolean enabled) { if (!mUseCABC) { return false; } putBoolean(CMSettings.System.DISPLAY_CABC, enabled); return true; } boolean isColorEnhancementEnabled() { return mUseColorEnhancement && getBoolean(CMSettings.System.DISPLAY_COLOR_ENHANCE, mDefaultColorEnhancement); } boolean setColorEnhancementEnabled(boolean enabled) { if (!mUseColorEnhancement) { return false; } putBoolean(CMSettings.System.DISPLAY_COLOR_ENHANCE, enabled); return true; } float[] getColorAdjustment() { if (!mUseColorAdjustment) { return getDefaultAdjustment(); } float[] cur = new float[3]; if (!parseColorAdjustment(getString(CMSettings.System.DISPLAY_COLOR_ADJUSTMENT), cur)) { // clear it out if invalid cur = getDefaultAdjustment(); saveColorAdjustmentString(cur); } return cur; } boolean setColorAdjustment(float[] adj) { // sanity check if (!mUseColorAdjustment || !validateColors(adj)) { return false; } saveColorAdjustmentString(adj); return true; } private void saveColorAdjustmentString(final float[] adj) { StringBuilder sb = new StringBuilder(); sb.append(adj[0]).append(" ").append(adj[1]).append(" ").append(adj[2]); putString(CMSettings.System.DISPLAY_COLOR_ADJUSTMENT, sb.toString()); } boolean hasColorAdjustment() { return mUseColorAdjustment; } private static float[] getDefaultAdjustment() { return new float[] { 1.0f, 1.0f, 1.0f }; } private void copyColors(float[] src, float[] dst) { if (src != null && dst != null && src.length == 3 && dst.length == 3) { dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; } } }