diff --git a/api/cm_current.txt b/api/cm_current.txt index ff84505..5992f3f 100644 --- a/api/cm_current.txt +++ b/api/cm_current.txt @@ -529,6 +529,53 @@ package cyanogenmod.hardware { method public boolean onTransact(int, android.os.Parcel, android.os.Parcel, int) throws android.os.RemoteException; } + public class LiveDisplayConfig implements android.os.Parcelable { + ctor public LiveDisplayConfig(java.util.BitSet, int, int, int, boolean, boolean, boolean, boolean); + method public int describeContents(); + method public boolean getDefaultAutoContrast(); + method public boolean getDefaultAutoOutdoorMode(); + method public boolean getDefaultCABC(); + method public boolean getDefaultColorEnhancement(); + method public int getDefaultDayTemperature(); + method public int getDefaultMode(); + method public int getDefaultNightTemperature(); + method public boolean hasFeature(int); + method public boolean isAvailable(); + method public void writeToParcel(android.os.Parcel, int); + } + + public class LiveDisplayManager { + method public float[] getColorAdjustment(); + method public cyanogenmod.hardware.LiveDisplayConfig getConfig(); + method public int getDayColorTemperature(); + method public static synchronized cyanogenmod.hardware.LiveDisplayManager getInstance(android.content.Context); + method public int getMode(); + method public int getNightColorTemperature(); + method public boolean isAutoContrastEnabled(); + method public boolean isAutomaticOutdoorModeEnabled(); + method public boolean isCABCEnabled(); + method public boolean isColorEnhancementEnabled(); + method public boolean setAutoContrastEnabled(boolean); + method public boolean setAutomaticOutdoorModeEnabled(boolean); + method public boolean setCABCEnabled(boolean); + method public boolean setColorAdjustment(float[]); + method public boolean setColorEnhancementEnabled(boolean); + method public boolean setDayColorTemperature(int); + method public boolean setMode(int); + method public boolean setNightColorTemperature(int); + field public static final int FEATURE_AUTO_CONTRAST = 11; // 0xb + field public static final int FEATURE_CABC = 10; // 0xa + field public static final int FEATURE_COLOR_ADJUSTMENT = 13; // 0xd + field public static final int FEATURE_COLOR_ENHANCEMENT = 12; // 0xc + field public static final int FEATURE_DISPLAY_MODES = 15; // 0xf + field public static final int FEATURE_MANAGED_OUTDOOR_MODE = 14; // 0xe + field public static final int MODE_AUTO = 2; // 0x2 + field public static final int MODE_DAY = 4; // 0x4 + field public static final int MODE_NIGHT = 1; // 0x1 + field public static final int MODE_OFF = 0; // 0x0 + field public static final int MODE_OUTDOOR = 3; // 0x3 + } + public abstract class ThermalListenerCallback extends cyanogenmod.hardware.IThermalListenerCallback.Stub { ctor public ThermalListenerCallback(); } @@ -613,6 +660,7 @@ package cyanogenmod.platform { field public static final java.lang.String HARDWARE_ABSTRACTION_ACCESS = "cyanogenmod.permission.HARDWARE_ABSTRACTION_ACCESS"; field public static final java.lang.String LIVE_LOCK_SCREEN_MANAGER_ACCESS = "cyanogenmod.permission.LIVE_LOCK_SCREEN_MANAGER_ACCESS"; field public static final java.lang.String MANAGE_ALARMS = "cyanogenmod.permission.MANAGE_ALARMS"; + field public static final java.lang.String MANAGE_LIVEDISPLAY = "cyanogenmod.permission.MANAGE_LIVEDISPLAY"; field public static final java.lang.String MANAGE_PERSISTENT_STORAGE = "cyanogenmod.permission.MANAGE_PERSISTENT_STORAGE"; field public static final java.lang.String MODIFY_MSIM_PHONE_STATE = "cyanogenmod.permission.MODIFY_MSIM_PHONE_STATE"; field public static final java.lang.String MODIFY_NETWORK_SETTINGS = "cyanogenmod.permission.MODIFY_NETWORK_SETTINGS"; @@ -795,6 +843,7 @@ package cyanogenmod.providers { public final class CMSettings { ctor public CMSettings(); field public static final java.lang.String ACTION_DATA_USAGE = "cyanogenmod.settings.ACTION_DATA_USAGE"; + field public static final java.lang.String ACTION_LIVEDISPLAY_SETTINGS = "cyanogenmod.settings.LIVEDISPLAY_SETTINGS"; field public static final java.lang.String AUTHORITY = "cmsettings"; } @@ -868,10 +917,12 @@ package cyanogenmod.providers { field public static final android.net.Uri CONTENT_URI; field public static final java.lang.String DIALER_OPENCNAM_ACCOUNT_SID = "dialer_opencnam_account_sid"; field public static final java.lang.String DIALER_OPENCNAM_AUTH_TOKEN = "dialer_opencnam_auth_token"; + field public static final java.lang.String DISPLAY_AUTO_CONTRAST = "display_auto_contrast"; field public static final java.lang.String DISPLAY_AUTO_OUTDOOR_MODE = "display_auto_outdoor_mode"; + field public static final java.lang.String DISPLAY_CABC = "display_low_power"; field public static final java.lang.String DISPLAY_COLOR_ADJUSTMENT = "display_color_adjustment"; field public static final java.lang.String DISPLAY_COLOR_ENHANCE = "display_color_enhance"; - field public static final java.lang.String DISPLAY_LOW_POWER = "display_low_power"; + field public static final deprecated java.lang.String DISPLAY_LOW_POWER = "display_low_power"; field public static final java.lang.String DISPLAY_TEMPERATURE_DAY = "display_temperature_day"; field public static final java.lang.String DISPLAY_TEMPERATURE_MODE = "display_temperature_mode"; field public static final java.lang.String DISPLAY_TEMPERATURE_NIGHT = "display_temperature_night"; diff --git a/cm/lib/main/java/org/cyanogenmod/platform/internal/display/AmbientLuxObserver.java b/cm/lib/main/java/org/cyanogenmod/platform/internal/display/AmbientLuxObserver.java new file mode 100644 index 0000000..32c7e70 --- /dev/null +++ b/cm/lib/main/java/org/cyanogenmod/platform/internal/display/AmbientLuxObserver.java @@ -0,0 +1,254 @@ +/* + * 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.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +import java.io.PrintWriter; +import java.util.Iterator; +import java.util.LinkedList; + +public class AmbientLuxObserver { + + private static final String TAG = "AmbientLuxObserver"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final Sensor mLightSensor; + private final SensorManager mSensorManager; + + private final float mThresholdLux; + private final int mThresholdDuration; + + private boolean mLightSensorEnabled = false; + private int mLightSensorRate; + + private float mAmbientLux = 0.0f; + + private static final int LOW = 0; + private static final int HIGH = 1; + + private int mState = LOW; + + private final AmbientLuxHandler mLuxHandler; + + private TransitionListener mCallback; + + private final TimedMovingAverageRingBuffer mRingBuffer; + + public interface TransitionListener { + public void onTransition(int state, float ambientLux); + } + + public AmbientLuxObserver(Context context, Looper looper, + float thresholdLux, int thresholdDuration) { + mLuxHandler = new AmbientLuxHandler(looper); + mThresholdLux = thresholdLux; + mThresholdDuration = thresholdDuration; + mRingBuffer = new TimedMovingAverageRingBuffer(thresholdDuration); + + mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); + mLightSensorRate = context.getResources().getInteger( + com.android.internal.R.integer.config_autoBrightnessLightSensorRate); + } + + private class AmbientLuxHandler extends Handler { + + private static final int MSG_UPDATE_LUX = 0; + private static final int MSG_TRANSITION = 1; + + AmbientLuxHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + int direction = 0; + float lux = 0.0f; + + synchronized (AmbientLuxObserver.this) { + switch (msg.what) { + case MSG_UPDATE_LUX: + lux = (Float) msg.obj; + mRingBuffer.add(lux); + + // FALL THRU + + case MSG_TRANSITION: + mAmbientLux = mRingBuffer.getAverage(); + + if (DEBUG) { + Log.d(TAG, "lux= " + lux + " mState=" + mState + + " mAmbientLux=" + mAmbientLux); + } + + direction = mAmbientLux >= mThresholdLux ? HIGH : LOW; + if (mState != direction) { + mState = direction; + if (mCallback != null) { + mCallback.onTransition(mState, mAmbientLux); + } + } + + // check again in case we didn't get any + // more readings because the sensor settled + if (mRingBuffer.size() > 1) { + sendEmptyMessageDelayed(MSG_TRANSITION, mThresholdDuration / 2); + } + break; + } + } + } + + void clear() { + removeCallbacksAndMessages(null); + } + }; + + private final SensorEventListener mListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent event) { + if (mLightSensorEnabled) { + Message.obtain(mLuxHandler, AmbientLuxHandler.MSG_UPDATE_LUX, + event.values[0]).sendToTarget(); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // Not used. + } + }; + + public synchronized int getState() { + return mState; + } + + public synchronized void setTransitionListener(TransitionListener callback) { + mCallback = callback; + enableLightSensor(callback != null); + } + + private void enableLightSensor(boolean enable) { + if (enable && !mLightSensorEnabled) { + mAmbientLux = 0.0f; + mState = LOW; + mLightSensorEnabled = true; + mRingBuffer.clear(); + mSensorManager.registerListener(mListener, mLightSensor, + mLightSensorRate * 1000, mLuxHandler); + } else if (!enable && mLightSensorEnabled) { + mLightSensorEnabled = false; + mSensorManager.unregisterListener(mListener); + mLuxHandler.clear(); + } + } + + public void dump(PrintWriter pw) { + pw.println(); + pw.println(" AmbientLuxObserver State:"); + pw.println(" mLightSensorEnabled=" + mLightSensorEnabled); + pw.println(" mState=" + mState); + pw.println(" mAmbientLux=" + mAmbientLux); + pw.println(" mRingBuffer=" + mRingBuffer.toString()); + } + + /** + * Calculates a simple moving average based on a fixed + * duration sliding window. This is useful for dampening + * erratic sensors and rolling thru transitional periods + * smoothly. + */ + private static class TimedMovingAverageRingBuffer { + + private final LinkedList mRing = new LinkedList(); + + private final int mPeriod; + + private float mTotal = 0.0f; + + private static class Sample { + public final long mTimestamp; + public final float mValue; + public Sample (long timestamp, float value) { + mTimestamp = timestamp; + mValue = value; + } + + @Override + public String toString() { + return "(" + mValue + ", " + mTimestamp + ")"; + } + } + + public TimedMovingAverageRingBuffer(int period) { + mPeriod = period; + } + + public synchronized void add(float sample) { + expire(); + if (sample == 0.0f && mRing.size() == 0) { + return; + } + mRing.offer(new Sample(System.currentTimeMillis(), sample)); + mTotal += sample; + } + + public synchronized int size() { + return mRing.size(); + } + + public synchronized float getAverage() { + expire(); + return mRing.size() == 0 ? 0.0f : (mTotal / mRing.size()); + } + + public synchronized void clear() { + mRing.clear(); + mTotal = 0.0f; + } + + private void expire() { + long now = System.currentTimeMillis(); + while (mRing.size() > 1 && + ((now - mRing.peek().mTimestamp) > mPeriod)) { + mTotal -= mRing.pop().mValue; + } + } + + @Override + public synchronized String toString() { + expire(); + StringBuilder sb = new StringBuilder(); + for (Iterator i = mRing.iterator(); i.hasNext();) { + if (sb.length() > 0) { + sb.append(", "); + } + sb.append(i.next()); + } + return "average=" + getAverage() + " length=" + mRing.size() + + " mRing=[" + sb.toString() + "]"; + } + } +} diff --git a/cm/lib/main/java/org/cyanogenmod/platform/internal/display/ColorTemperatureController.java b/cm/lib/main/java/org/cyanogenmod/platform/internal/display/ColorTemperatureController.java new file mode 100644 index 0000000..9c4253e --- /dev/null +++ b/cm/lib/main/java/org/cyanogenmod/platform/internal/display/ColorTemperatureController.java @@ -0,0 +1,272 @@ +/* + * 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 static cyanogenmod.hardware.LiveDisplayManager.MODE_AUTO; +import static cyanogenmod.hardware.LiveDisplayManager.MODE_DAY; +import static cyanogenmod.hardware.LiveDisplayManager.MODE_NIGHT; +import static cyanogenmod.hardware.LiveDisplayManager.MODE_OFF; + +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.content.Context; +import android.net.Uri; +import android.os.Handler; +import android.os.UserHandle; +import android.text.format.DateUtils; +import android.util.MathUtils; +import android.util.Slog; + +import com.android.server.twilight.TwilightState; + +import java.io.PrintWriter; +import java.util.BitSet; + +import cyanogenmod.providers.CMSettings; +import cyanogenmod.util.ColorUtils; + +public class ColorTemperatureController extends LiveDisplayFeature { + + private ValueAnimator mAnimator; + private DisplayHardwareController mDisplayHardware; + + private boolean mUseTemperatureAdjustment; + + private int mDefaultDayTemperature; + private int mDefaultNightTemperature; + + private int mDayTemperature; + private int mNightTemperature; + + private static final long TWILIGHT_ADJUSTMENT_TIME = DateUtils.HOUR_IN_MILLIS * 1; + + private static final int OFF_TEMPERATURE = 6500; + + private int mColorTemperature = OFF_TEMPERATURE; + + public ColorTemperatureController(Context context, Handler handler, + DisplayHardwareController displayHardware) { + super(context, handler); + mDisplayHardware = displayHardware; + } + + @Override + public boolean onStart() { + if (!mDisplayHardware.hasColorAdjustment()) { + return false; + } + + mUseTemperatureAdjustment = true; + + mDefaultDayTemperature = mContext.getResources().getInteger( + org.cyanogenmod.platform.internal.R.integer.config_dayColorTemperature); + mDefaultNightTemperature = mContext.getResources().getInteger( + org.cyanogenmod.platform.internal.R.integer.config_nightColorTemperature); + + registerSettings( + CMSettings.System.getUriFor(CMSettings.System.DISPLAY_TEMPERATURE_DAY), + CMSettings.System.getUriFor(CMSettings.System.DISPLAY_TEMPERATURE_NIGHT)); + return true; + } + + void getCapabilities(final BitSet caps) { + if (mUseTemperatureAdjustment) { + caps.set(MODE_AUTO); + caps.set(MODE_DAY); + caps.set(MODE_NIGHT); + } + } + + int getDefaultDayTemperature() { + return mDefaultDayTemperature; + } + + int getDefaultNightTemperature() { + return mDefaultNightTemperature; + } + + int getColorTemperature() { + return mColorTemperature; + } + + int getDayColorTemperature() { + return getInt(CMSettings.System.DISPLAY_TEMPERATURE_DAY, + mDefaultDayTemperature); + } + + void setDayColorTemperature(int temperature) { + putInt(CMSettings.System.DISPLAY_TEMPERATURE_DAY, temperature); + } + + int getNightColorTemperature() { + return getInt(CMSettings.System.DISPLAY_TEMPERATURE_NIGHT, + mDefaultNightTemperature); + } + + void setNightColorTemperature(int temperature) { + putInt(CMSettings.System.DISPLAY_TEMPERATURE_NIGHT, temperature); + } + + @Override + public void onModeChanged(int mode) { + super.onModeChanged(mode); + updateColorTemperature(); + } + + @Override + public synchronized void onSettingsChanged(Uri uri) { + mDayTemperature = getDayColorTemperature(); + mNightTemperature = getNightColorTemperature(); + updateColorTemperature(); + } + + @Override + public void onTwilightUpdated(TwilightState twilight) { + super.onTwilightUpdated(twilight); + mHandler.post(mTransitionRunnable); + } + + @Override + public void dump(PrintWriter pw) { + pw.println(); + pw.println("ColorTemperatureController Configuration:"); + pw.println(" mDayTemperature=" + mDayTemperature); + pw.println(" mNightTemperature=" + mNightTemperature); + pw.println(); + pw.println(" ColorTemperatureController State:"); + pw.println(" mColorTemperature=" + mColorTemperature); + if (getTwilight() != null) { + pw.println(" mTwilight=" + getTwilight().toString()); + } + pw.println(" transitioning=" + mHandler.hasCallbacks(mTransitionRunnable)); + } + + private final Runnable mTransitionRunnable = new Runnable() { + @Override + public void run() { + synchronized (ColorTemperatureController.this) { + updateColorTemperature(); + + boolean transition = getMode() == MODE_AUTO && + mColorTemperature != mDayTemperature && + mColorTemperature != mNightTemperature; + + if (transition) { + // fire again in a minute + mHandler.postDelayed(mTransitionRunnable, DateUtils.MINUTE_IN_MILLIS); + } + } + } + }; + + private void updateColorTemperature() { + mHandler.removeCallbacks(mTransitionRunnable); + + int temperature = mDayTemperature; + int mode = getMode(); + + if (mode == MODE_OFF || isLowPowerMode()) { + temperature = OFF_TEMPERATURE; + } else if (mode == MODE_NIGHT) { + temperature = mNightTemperature; + } else if (mode == MODE_AUTO) { + temperature = getTwilightK(); + } + + if (DEBUG) { + Slog.d(TAG, "updateColorTemperatureLocked mode=" + mode + + " temperature=" + temperature + " mColorTemperature=" + mColorTemperature); + } + + if (mAnimator != null) { + mAnimator.cancel(); + mAnimator.removeAllUpdateListeners(); + } + mAnimator = ValueAnimator.ofInt(mColorTemperature, temperature); + mAnimator.setDuration(Math.abs(mColorTemperature - temperature) / 2); + mAnimator.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(final ValueAnimator animation) { + mHandler.post(new Runnable() { + @Override + public void run() { + setDisplayTemperature((Integer)animation.getAnimatedValue()); + } + }); + } + }); + mAnimator.start(); + } + + + private synchronized void setDisplayTemperature(int temperature) { + mColorTemperature = temperature; + + final float[] rgb = ColorUtils.temperatureToRGB(temperature); + mDisplayHardware.setAdditionalAdjustment(rgb); + + if (DEBUG) { + Slog.d(TAG, "Adjust display temperature to " + temperature + "K"); + } + } + + /** + * Where is the sun anyway? This calculation determines day or night, and scales + * the value around sunset/sunrise for a smooth transition. + * + * @param now + * @param sunset + * @param sunrise + * @return float between 0 and 1 + */ + private static float adj(long now, long sunset, long sunrise) { + if (sunset < 0 || sunrise < 0 + || now < sunset || now > sunrise) { + return 1.0f; + } + + if (now < sunset + TWILIGHT_ADJUSTMENT_TIME) { + return MathUtils.lerp(1.0f, 0.0f, + (float)(now - sunset) / TWILIGHT_ADJUSTMENT_TIME); + } + + if (now > sunrise - TWILIGHT_ADJUSTMENT_TIME) { + return MathUtils.lerp(1.0f, 0.0f, + (float)(sunrise - now) / TWILIGHT_ADJUSTMENT_TIME); + } + + return 0.0f; + } + + /** + * Determine the color temperature we should use for the display based on + * the position of the sun. + * + * @return color temperature in Kelvin + */ + private int getTwilightK() { + float adjustment = 1.0f; + final TwilightState twilight = getTwilight(); + + if (twilight != null) { + final long now = System.currentTimeMillis(); + adjustment = adj(now, twilight.getYesterdaySunset(), twilight.getTodaySunrise()) * + adj(now, twilight.getTodaySunset(), twilight.getTomorrowSunrise()); + } + + return (int)MathUtils.lerp(mNightTemperature, mDayTemperature, adjustment); + } +} diff --git a/cm/lib/main/java/org/cyanogenmod/platform/internal/display/DisplayHardwareController.java b/cm/lib/main/java/org/cyanogenmod/platform/internal/display/DisplayHardwareController.java new file mode 100644 index 0000000..0189ccd --- /dev/null +++ b/cm/lib/main/java/org/cyanogenmod/platform/internal/display/DisplayHardwareController.java @@ -0,0 +1,435 @@ +/* + * 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.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.Slog; + +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 CMHardwareManager mHardware; + + // hardware capabilities + private boolean mUseAutoContrast; + private boolean mUseColorAdjustment; + private boolean mUseColorEnhancement; + private boolean mUseCABC; + + // default values + private boolean mDefaultAutoContrast; + private boolean mDefaultColorEnhancement; + private boolean mDefaultCABC; + + // current values + private boolean mAutoContrast; + private boolean mColorEnhancement; + private boolean mCABC; + + // color adjustment holders + private final float[] mColorAdjustment = new float[] { 1.0f, 1.0f, 1.0f }; + private final float[] mAdditionalAdjustment = new float[] { 1.0f, 1.0f, 1.0f }; + private final float[] mRGB = new float[] { 1.0f, 1.0f, 1.0f }; + + // 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); + } + + @Override + public boolean onStart() { + final ArrayList settings = new ArrayList(); + + mHardware = CMHardwareManager.getInstance(mContext); + mUseCABC = mHardware + .isSupported(CMHardwareManager.FEATURE_ADAPTIVE_BACKLIGHT); + if (mUseCABC) { + mDefaultCABC = mContext.getResources().getBoolean( + org.cyanogenmod.platform.internal.R.bool.config_defaultCABC); + mCABC = mHardware.get(CMHardwareManager.FEATURE_ADAPTIVE_BACKLIGHT); + settings.add(DISPLAY_CABC); + } + + mUseColorEnhancement = mHardware + .isSupported(CMHardwareManager.FEATURE_COLOR_ENHANCEMENT); + if (mUseColorEnhancement) { + mDefaultColorEnhancement = mContext.getResources().getBoolean( + org.cyanogenmod.platform.internal.R.bool.config_defaultColorEnhancement); + mColorEnhancement = mHardware.get(CMHardwareManager.FEATURE_COLOR_ENHANCEMENT); + settings.add(DISPLAY_COLOR_ENHANCE); + } + + mUseAutoContrast = mHardware + .isSupported(CMHardwareManager.FEATURE_AUTO_CONTRAST); + if (mUseAutoContrast) { + mDefaultAutoContrast = mContext.getResources().getBoolean( + org.cyanogenmod.platform.internal.R.bool.config_defaultAutoContrast); + mAutoContrast = mHardware.get(CMHardwareManager.FEATURE_AUTO_CONTRAST); + settings.add(DISPLAY_AUTO_CONTRAST); + } + + mUseColorAdjustment = mHardware + .isSupported(CMHardwareManager.FEATURE_DISPLAY_COLOR_CALIBRATION); + if (mUseColorAdjustment) { + settings.add(DISPLAY_COLOR_ADJUSTMENT); + } + + if (settings.size() == 0) { + return false; + } + + registerSettings(settings.toArray(new Uri[settings.size()])); + return true; + } + + @Override + void 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); + } + } + + @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)) { + System.arraycopy( + parseColorAdjustment(getString(CMSettings.System.DISPLAY_COLOR_ADJUSTMENT)), + 0, mColorAdjustment, 0, 3); + updateColorAdjustment(); + } + } + + @Override + public synchronized void onLowPowerModeChanged(boolean lowPowerMode) { + updateCABCMode(); + updateAutoContrast(); + updateColorEnhancement(); + } + + @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(); + pw.println(" DisplayHardwareController State:"); + pw.println(" mAutoContrast=" + mAutoContrast); + pw.println(" mColorEnhancement=" + mColorEnhancement); + pw.println(" mCABC=" + mCABC); + pw.println(" mColorAdjustment=" + Arrays.toString(mColorAdjustment)); + pw.println(" mAdditionalAdjustment=" + Arrays.toString(mAdditionalAdjustment)); + pw.println(" mRGB=" + Arrays.toString(mRGB)); + } + + boolean hasColorAdjustment() { + return mUseColorAdjustment; + } + + /** + * Additional adjustments provided by night mode + * + * @param adj + */ + synchronized void setAdditionalAdjustment(float[] adj) { + if (DEBUG) { + Slog.d(TAG, "setAdditionalAdjustment: " + Arrays.toString(adj)); + } + // Sanity check this so we don't mangle the display + if (adj != null && adj.length == 3 && + !(adj[0] <= 0.0f && adj[1] <= 0.0f && adj[2] <= 0.0f)) { + for (int i = 0; i < 3; i++) { + if (adj[i] > 1.0f) { + adj[i] = 1.0f; + } + } + System.arraycopy(adj, 0, mAdditionalAdjustment, 0, 3); + updateColorAdjustment(); + } else { + mAdditionalAdjustment[0] = 1.0f; + mAdditionalAdjustment[1] = 1.0f; + mAdditionalAdjustment[2] = 1.0f; + } + + } + + /** + * Automatic contrast optimization + */ + private synchronized void updateAutoContrast() { + if (!mUseAutoContrast) { + return; + } + + boolean value = getInt(CMSettings.System.DISPLAY_AUTO_CONTRAST, + (mDefaultAutoContrast ? 1 : 0)) == 1; + + boolean enabled = !isLowPowerMode() && value; + + if (enabled == mAutoContrast) { + return; + } + + mHardware.set(CMHardwareManager.FEATURE_AUTO_CONTRAST, enabled); + mAutoContrast = enabled; + } + + /** + * Color enhancement is optional + */ + private synchronized void updateColorEnhancement() { + if (!mUseColorEnhancement) { + return; + } + + boolean value = getInt(CMSettings.System.DISPLAY_COLOR_ENHANCE, + (mDefaultColorEnhancement ? 1 : 0)) == 1; + + boolean enabled = !isLowPowerMode() && value; + + if (enabled == mColorEnhancement) { + return; + } + + mHardware.set(CMHardwareManager.FEATURE_COLOR_ENHANCEMENT, enabled); + mColorEnhancement = enabled; + } + + /** + * Adaptive backlight / low power mode. Turn it off when under very bright light. + */ + private synchronized void updateCABCMode() { + if (!mUseCABC) { + return; + } + + boolean value = getInt(CMSettings.System.DISPLAY_CABC, + (mDefaultCABC ? 1 : 0)) == 1; + + boolean enabled = !isLowPowerMode() && value; + + if (enabled == mCABC) { + return; + } + + mHardware.set(CMHardwareManager.FEATURE_ADAPTIVE_BACKLIGHT, value); + mCABC = value; + } + + private synchronized void updateColorAdjustment() { + if (!mUseColorAdjustment) { + return; + } + + final float[] rgb = new float[] { 1.0f, 1.0f, 1.0f }; + + if (!isLowPowerMode()) { + + System.arraycopy(mAdditionalAdjustment, 0, rgb, 0, 3); + rgb[0] *= mColorAdjustment[0]; + rgb[1] *= mColorAdjustment[1]; + rgb[2] *= mColorAdjustment[2]; + } + + if (rgb[0] == mRGB[0] && rgb[1] == mRGB[1] && rgb[2] == mRGB[2]) { + // no changes + return; + } + + if (DEBUG) { + Slog.d(TAG, "updateColorAdjustment: " + Arrays.toString(rgb)); + } + + int max = mHardware.getDisplayColorCalibrationMax(); + mHardware.setDisplayColorCalibration(new int[] { + (int) Math.ceil(rgb[0] * max), + (int) Math.ceil(rgb[1] * max), + (int) Math.ceil(rgb[2] * max) + }); + System.arraycopy(rgb, 0, mRGB, 0, 3); + + screenRefresh(); + } + + /** + * 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); + } + } + + boolean getDefaultCABC() { + return mDefaultCABC; + } + + boolean getDefaultAutoContrast() { + return mDefaultAutoContrast; + } + + boolean getDefaultColorEnhancement() { + return mDefaultColorEnhancement; + } + + boolean isAutoContrastEnabled() { + return mUseAutoContrast && + getInt(CMSettings.System.DISPLAY_AUTO_CONTRAST, + (mDefaultAutoContrast ? 1 : 0)) == 1; + } + + boolean setAutoContrastEnabled(boolean enabled) { + if (!mUseAutoContrast) { + return false; + } + putInt(CMSettings.System.DISPLAY_AUTO_CONTRAST, enabled ? 1 : 0); + return true; + } + + boolean isCABCEnabled() { + return mUseCABC && + getInt(CMSettings.System.DISPLAY_CABC, + (mDefaultCABC ? 1 : 0)) == 1; + } + + boolean setCABCEnabled(boolean enabled) { + if (!mUseCABC) { + return false; + } + putInt(CMSettings.System.DISPLAY_CABC, enabled ? 1 : 0); + return true; + } + + boolean isColorEnhancementEnabled() { + return mUseColorEnhancement && + getInt(CMSettings.System.DISPLAY_COLOR_ENHANCE, + (mDefaultColorEnhancement ? 1 : 0)) == 1; + } + + boolean setColorEnhancementEnabled(boolean enabled) { + if (!mUseColorEnhancement) { + return false; + } + putInt(CMSettings.System.DISPLAY_COLOR_ENHANCE, enabled ? 1 : 0); + return true; + } + + + float[] getColorAdjustment() { + if (!mUseColorAdjustment) { + return new float[] { 1.0f, 1.0f, 1.0f }; + } + return parseColorAdjustment(getString(CMSettings.System.DISPLAY_COLOR_ADJUSTMENT)); + } + + boolean setColorAdjustment(float[] adj) { + // sanity check + if (!mUseColorAdjustment || adj.length != 3 || + adj[0] < 0 || adj[0] > 1.0f || + adj[1] < 0 || adj[1] > 1.0f || + adj[2] < 0 || adj[2] > 1.0f) { + return false; + } + StringBuilder sb = new StringBuilder(); + sb.append(adj[0]).append(" ").append(adj[1]).append(" ").append(adj[2]); + + putString(CMSettings.System.DISPLAY_COLOR_ADJUSTMENT, sb.toString()); + return true; + } + + /** + * Parse and sanity check an RGB triplet from a string. + */ + private float[] parseColorAdjustment(String rgbString) { + String[] adj = rgbString == null ? null : rgbString.split(" "); + float[] parsed = new float[3]; + + if (adj == null || adj.length != 3) { + adj = new String[] { "1.0", "1.0", "1.0" }; + } + + try { + parsed[0] = Float.parseFloat(adj[0]); + parsed[1] = Float.parseFloat(adj[1]); + parsed[2] = Float.parseFloat(adj[2]); + } catch (NumberFormatException e) { + Slog.e(TAG, e.getMessage(), e); + parsed[0] = 1.0f; + parsed[1] = 1.0f; + parsed[2] = 1.0f; + } + + // sanity check + for (int i = 0; i < parsed.length; i++) { + if (parsed[i] <= 0.0f || parsed[i] > 1.0f) { + parsed[i] = 1.0f; + } + } + return parsed; + } +} diff --git a/cm/lib/main/java/org/cyanogenmod/platform/internal/display/LiveDisplayFeature.java b/cm/lib/main/java/org/cyanogenmod/platform/internal/display/LiveDisplayFeature.java new file mode 100644 index 0000000..5b1e33c --- /dev/null +++ b/cm/lib/main/java/org/cyanogenmod/platform/internal/display/LiveDisplayFeature.java @@ -0,0 +1,158 @@ +/* + * 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.content.ContentResolver; +import android.content.Context; +import android.net.Uri; +import android.os.Handler; +import android.os.UserHandle; +import android.util.Log; + +import com.android.server.pm.UserContentObserver; +import com.android.server.twilight.TwilightState; + +import java.io.PrintWriter; +import java.util.BitSet; + +import cyanogenmod.providers.CMSettings; + +public abstract class LiveDisplayFeature { + + protected static final String TAG = "LiveDisplay"; + protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + protected final Context mContext; + protected final Handler mHandler; + + private TwilightState mTwilight; + private boolean mLowPowerMode = false; + private boolean mScreenOn = false; + private int mMode = 0; + + private final SettingsObserver mSettingsObserver; + + public LiveDisplayFeature(Context context, Handler handler) { + mContext = context; + mHandler = handler; + mSettingsObserver = new SettingsObserver(handler); + } + + public abstract boolean onStart(); + + public abstract void onSettingsChanged(Uri uri); + + public void onModeChanged(int mode) { + mMode = mode; + } + + public void onDisplayStateChanged(boolean screenOn) { + mScreenOn = screenOn; + } + + public void onLowPowerModeChanged(boolean lowPowerMode) { + mLowPowerMode = lowPowerMode; + } + + public void onTwilightUpdated(TwilightState twilight) { + mTwilight = twilight; + } + + public void onDestroy() { + mSettingsObserver.unregister(); + } + + public abstract void dump(PrintWriter pw); + + abstract void getCapabilities(final BitSet caps); + + protected final void registerSettings(Uri... settings) { + mSettingsObserver.register(settings); + onSettingsChanged(null); + } + + protected final int getInt(String setting, int defaultValue) { + return CMSettings.System.getIntForUser(mContext.getContentResolver(), + setting, defaultValue, UserHandle.USER_CURRENT); + } + + protected final void putInt(String setting, int value) { + CMSettings.System.putIntForUser(mContext.getContentResolver(), + setting, value, UserHandle.USER_CURRENT); + } + + protected final String getString(String setting) { + return CMSettings.System.getStringForUser(mContext.getContentResolver(), + setting, UserHandle.USER_CURRENT); + } + + protected final void putString(String setting, String value) { + CMSettings.System.putStringForUser(mContext.getContentResolver(), + setting, value, UserHandle.USER_CURRENT); + } + + protected final boolean isLowPowerMode() { + return mLowPowerMode; + } + + protected final int getMode() { + return mMode; + } + + protected final boolean isScreenOn() { + return mScreenOn; + } + + protected final TwilightState getTwilight() { + return mTwilight; + } + + protected final boolean isNight() { + return mTwilight != null && mTwilight.isNight(); + } + + final class SettingsObserver extends UserContentObserver { + + public SettingsObserver(Handler handler) { + super(handler); + } + + public void register(Uri... uris) { + final ContentResolver cr = mContext.getContentResolver(); + for (Uri uri : uris) { + cr.registerContentObserver(uri, false, this, UserHandle.USER_ALL); + } + + observe(); + } + + public void unregister() { + mContext.getContentResolver().unregisterContentObserver(this); + unobserve(); + } + + @Override + protected void update() { + onSettingsChanged(null); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + onSettingsChanged(uri); + } + } + +} diff --git a/cm/lib/main/java/org/cyanogenmod/platform/internal/display/LiveDisplayService.java b/cm/lib/main/java/org/cyanogenmod/platform/internal/display/LiveDisplayService.java new file mode 100644 index 0000000..68e01bb --- /dev/null +++ b/cm/lib/main/java/org/cyanogenmod/platform/internal/display/LiveDisplayService.java @@ -0,0 +1,523 @@ +/* + * 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.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.hardware.display.DisplayManager; +import android.net.Uri; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManagerInternal; +import android.os.Process; +import android.os.UserHandle; +import android.util.Log; +import android.view.Display; + +import com.android.server.LocalServices; +import com.android.server.ServiceThread; +import com.android.server.SystemService; +import com.android.server.pm.UserContentObserver; +import com.android.server.twilight.TwilightListener; +import com.android.server.twilight.TwilightManager; +import com.android.server.twilight.TwilightState; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Iterator; +import java.util.List; + +import cyanogenmod.app.CMContextConstants; +import cyanogenmod.hardware.ILiveDisplayService; +import cyanogenmod.hardware.LiveDisplayConfig; +import cyanogenmod.providers.CMSettings; + +import static cyanogenmod.hardware.LiveDisplayManager.*; + +/** + * LiveDisplay is an advanced set of features for improving + * display quality under various ambient conditions. + * + * The service is constructed with a set of LiveDisplayFeatures + * which provide capabilities such as outdoor mode, night mode, + * and calibration. It interacts with CMHardwareService to relay + * changes down to the lower layers. + */ +public class LiveDisplayService extends SystemService { + + private static final String TAG = "LiveDisplay"; + + private static final int MSG_MODE_CHANGED = 1; + private static final int MSG_DISPLAY_CHANGED = 2; + private static final int MSG_LOW_POWER_MODE_CHANGED = 3; + private static final int MSG_TWILIGHT_UPDATE = 4; + + private final Context mContext; + private final Handler mHandler; + private final ServiceThread mHandlerThread; + + private DisplayManager mDisplayManager; + private ModeObserver mModeObserver; + private TwilightManager mTwilightManager; + + private boolean mInitialized = false; + private boolean mAwaitingNudge = true; + private boolean mSunset = false; + + private boolean mLowPowerMode; + private int mDisplayState = -1; + + private final List mFeatures = new ArrayList(); + + private ColorTemperatureController mCTC; + private DisplayHardwareController mDHC; + private OutdoorModeController mOMC; + + private LiveDisplayConfig mConfig; + + public LiveDisplayService(Context context) { + super(context); + + mContext = context; + + // We want a slightly higher priority thread to handle these requests + mHandlerThread = new ServiceThread(TAG, + Process.THREAD_PRIORITY_DISPLAY + 1, false /*allowIo*/); + mHandlerThread.start(); + mHandler = new LiveDisplayHandler(mHandlerThread.getLooper()); + } + + @Override + public void onStart() { + if (mContext.getPackageManager().hasSystemFeature( + CMContextConstants.Features.LIVEDISPLAY)) { + publishBinderService(CMContextConstants.CM_LIVEDISPLAY_SERVICE, mBinder); + } else { + Log.wtf(TAG, "CM LiveDisplay service started by system server but feature xml not" + + " declared. Not publishing binder service!"); + } + } + + @Override + public void onBootPhase(int phase) { + if (phase == PHASE_BOOT_COMPLETED) { + + mAwaitingNudge = getSunsetCounter() < 1; + + mDHC = new DisplayHardwareController(mContext, mHandler); + mFeatures.add(mDHC); + + mCTC = new ColorTemperatureController(mContext, mHandler, mDHC); + mFeatures.add(mCTC); + + mOMC = new OutdoorModeController(mContext, mHandler); + mFeatures.add(mOMC); + + // Call onStart of each feature and get it's capabilities + final BitSet capabilities = new BitSet(); + for (Iterator it = mFeatures.iterator(); it.hasNext();) { + final LiveDisplayFeature feature = it.next(); + if (feature.onStart()) { + feature.getCapabilities(capabilities); + } else { + it.remove(); + } + } + + int defaultMode = mContext.getResources().getInteger( + org.cyanogenmod.platform.internal.R.integer.config_defaultLiveDisplayMode); + + mConfig = new LiveDisplayConfig(capabilities, defaultMode, + mCTC.getDefaultDayTemperature(), mCTC.getDefaultNightTemperature(), + mOMC.getDefaultAutoOutdoorMode(), mDHC.getDefaultAutoContrast(), + mDHC.getDefaultCABC(), mDHC.getDefaultColorEnhancement()); + + mDisplayManager = (DisplayManager) getContext().getSystemService( + Context.DISPLAY_SERVICE); + mDisplayManager.registerDisplayListener(mDisplayListener, null); + + PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class); + pmi.registerLowPowerModeObserver(mLowPowerModeListener); + + mTwilightManager = LocalServices.getService(TwilightManager.class); + mTwilightManager.registerListener(mTwilightListener, mHandler); + updateTwilight(); + + updateDisplayState(mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY).getState()); + + mModeObserver = new ModeObserver(mHandler); + mModeObserver.update(); + + mInitialized = true; + } + } + + private final IBinder mBinder = new ILiveDisplayService.Stub() { + + @Override + public LiveDisplayConfig getConfig() { + return mConfig; + } + + @Override + public int getMode() { + return mModeObserver.getMode(); + } + + @Override + public boolean setMode(int mode) { + mContext.enforceCallingOrSelfPermission( + cyanogenmod.platform.Manifest.permission.MANAGE_LIVEDISPLAY, null); + if (mConfig.hasFeature(mode) && mode >= MODE_FIRST && mode <= MODE_LAST) { + putInt(CMSettings.System.DISPLAY_TEMPERATURE_MODE, mode); + return true; + } + return false; + } + + @Override + public float[] getColorAdjustment() { + return mDHC.getColorAdjustment(); + } + + @Override + public boolean setColorAdjustment(float[] adj) { + mContext.enforceCallingOrSelfPermission( + cyanogenmod.platform.Manifest.permission.MANAGE_LIVEDISPLAY, null); + return mDHC.setColorAdjustment(adj); + } + + @Override + public boolean isAutoContrastEnabled() { + return mDHC.isAutoContrastEnabled(); + } + + @Override + public boolean setAutoContrastEnabled(boolean enabled) { + mContext.enforceCallingOrSelfPermission( + cyanogenmod.platform.Manifest.permission.MANAGE_LIVEDISPLAY, null); + return mDHC.setAutoContrastEnabled(enabled); + } + + @Override + public boolean isCABCEnabled() { + return mDHC.isCABCEnabled(); + } + + @Override + public boolean setCABCEnabled(boolean enabled) { + mContext.enforceCallingOrSelfPermission( + cyanogenmod.platform.Manifest.permission.MANAGE_LIVEDISPLAY, null); + return mDHC.setCABCEnabled(enabled); + } + + @Override + public boolean isColorEnhancementEnabled() { + return mDHC.isColorEnhancementEnabled(); + } + + @Override + public boolean setColorEnhancementEnabled(boolean enabled) { + mContext.enforceCallingOrSelfPermission( + cyanogenmod.platform.Manifest.permission.MANAGE_LIVEDISPLAY, null); + return mDHC.setColorEnhancementEnabled(enabled); + } + + @Override + public boolean isAutomaticOutdoorModeEnabled() { + return mOMC.isAutomaticOutdoorModeEnabled(); + } + + @Override + public boolean setAutomaticOutdoorModeEnabled(boolean enabled) { + mContext.enforceCallingOrSelfPermission( + cyanogenmod.platform.Manifest.permission.MANAGE_LIVEDISPLAY, null); + return mOMC.setAutomaticOutdoorModeEnabled(enabled); + } + + @Override + public int getDayColorTemperature() { + return mCTC.getDayColorTemperature(); + } + + @Override + public boolean setDayColorTemperature(int temperature) { + mContext.enforceCallingOrSelfPermission( + cyanogenmod.platform.Manifest.permission.MANAGE_LIVEDISPLAY, null); + mCTC.setDayColorTemperature(temperature); + return true; + } + + @Override + public int getNightColorTemperature() { + return mCTC.getNightColorTemperature(); + } + + @Override + public boolean setNightColorTemperature(int temperature) { + mContext.enforceCallingOrSelfPermission( + cyanogenmod.platform.Manifest.permission.MANAGE_LIVEDISPLAY, null); + mCTC.setNightColorTemperature(temperature); + return true; + } + + @Override + public int getColorTemperature() { + return mCTC.getColorTemperature(); + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println(); + pw.println("LiveDisplay Service State:"); + pw.println(" mMode=" + mModeObserver.getMode()); + pw.println(" mDisplayState=" + mDisplayState); + pw.println(" mAwaitingNudge=" + mAwaitingNudge); + pw.println(" mConfig=" + mConfig.toString()); + + for (int i = 0; i < mFeatures.size(); i++) { + mFeatures.get(i).dump(pw); + } + } + }; + + // Listener for screen on/off events + private final DisplayManager.DisplayListener mDisplayListener = + new DisplayManager.DisplayListener() { + @Override + public void onDisplayAdded(int displayId) { + } + + @Override + public void onDisplayRemoved(int displayId) { + } + + @Override + public void onDisplayChanged(int displayId) { + if (displayId == Display.DEFAULT_DISPLAY) { + mHandler.obtainMessage(MSG_DISPLAY_CHANGED, + mDisplayManager.getDisplay(displayId).getState(), 0).sendToTarget(); + } + } + }; + + + // Display postprocessing can have power impact. + private PowerManagerInternal.LowPowerModeListener mLowPowerModeListener = + new PowerManagerInternal.LowPowerModeListener() { + @Override + public void onLowPowerModeChanged(boolean lowPowerMode) { + if (lowPowerMode != mLowPowerMode) { + mLowPowerMode = lowPowerMode; + mHandler.obtainMessage(MSG_LOW_POWER_MODE_CHANGED, + (lowPowerMode ? 1 : 0), 0).sendToTarget(); + } + } + }; + + // Watch for mode changes + private final class ModeObserver extends UserContentObserver { + + private final Uri MODE_SETTING = + CMSettings.System.getUriFor(CMSettings.System.DISPLAY_TEMPERATURE_MODE); + + ModeObserver(Handler handler) { + super(handler); + + final ContentResolver cr = mContext.getContentResolver(); + cr.registerContentObserver(MODE_SETTING, false, this, UserHandle.USER_ALL); + + observe(); + } + + @Override + protected void update() { + mHandler.obtainMessage(MSG_MODE_CHANGED, getMode(), 0).sendToTarget(); + } + + int getMode() { + return getInt(CMSettings.System.DISPLAY_TEMPERATURE_MODE, + mConfig.getDefaultMode()); + } + } + + // Night watchman + private final TwilightListener mTwilightListener = new TwilightListener() { + @Override + public void onTwilightStateChanged() { + mHandler.obtainMessage(MSG_TWILIGHT_UPDATE, + mTwilightManager.getCurrentState()).sendToTarget(); + } + }; + + private int getSunsetCounter() { + // Counter used to determine when we should tell the user about this feature. + // If it's not used after 3 sunsets, we'll show the hint once. + return CMSettings.System.getIntForUser(mContext.getContentResolver(), + CMSettings.System.LIVE_DISPLAY_HINTED, + -3, + UserHandle.USER_CURRENT); + } + + + private void updateSunsetCounter(int count) { + CMSettings.System.putIntForUser(mContext.getContentResolver(), + CMSettings.System.LIVE_DISPLAY_HINTED, + count, + UserHandle.USER_CURRENT); + mAwaitingNudge = count > 0; + } + + private void stopNudgingMe() { + if (mAwaitingNudge) { + updateSunsetCounter(1); + } + } + + /** + * Show a friendly notification to the user about the potential benefits of decreasing + * blue light at night. Do this only once if the feature has not been used after + * three sunsets. It would be great to enable this by default, but we don't want + * the change of screen color to be considered a "bug" by a user who doesn't + * understand what's happening. + * + * @param state + */ + private void nudge() { + final TwilightState twilight = mTwilightManager.getCurrentState(); + if (!mAwaitingNudge || twilight == null) { + return; + } + + int counter = getSunsetCounter(); + + // check if we should send the hint only once after sunset + boolean transition = twilight.isNight() && !mSunset; + mSunset = twilight.isNight(); + if (!transition) { + return; + } + + if (counter <= 0) { + counter++; + updateSunsetCounter(counter); + } + if (counter == 0) { + //show the notification and don't come back here + final Intent intent = new Intent(CMSettings.ACTION_LIVEDISPLAY_SETTINGS); + PendingIntent result = PendingIntent.getActivity( + mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + Notification.Builder builder = new Notification.Builder(mContext) + .setContentTitle(mContext.getResources().getString( + org.cyanogenmod.platform.internal.R.string.live_display_title)) + .setContentText(mContext.getResources().getString( + org.cyanogenmod.platform.internal.R.string.live_display_hint)) + .setSmallIcon(org.cyanogenmod.platform.internal.R.drawable.ic_livedisplay_notif) + .setStyle(new Notification.BigTextStyle().bigText(mContext.getResources() + .getString( + org.cyanogenmod.platform.internal.R.string.live_display_hint))) + .setContentIntent(result) + .setAutoCancel(true); + + NotificationManager nm = + (NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE); + nm.notifyAsUser(null, 1, builder.build(), UserHandle.CURRENT); + + updateSunsetCounter(1); + } + } + + private int getInt(String setting, int defValue) { + return CMSettings.System.getIntForUser(mContext.getContentResolver(), + setting, defValue, UserHandle.USER_CURRENT); + } + + private void putInt(String setting, int value) { + CMSettings.System.putIntForUser(mContext.getContentResolver(), + setting, value, UserHandle.USER_CURRENT); + } + + private synchronized void updateTwilight() { + final TwilightState twilight = mTwilightManager.getCurrentState(); + for (int i = 0; i < mFeatures.size(); i++) { + mFeatures.get(i).onTwilightUpdated(twilight); + } + } + + private synchronized void updateDisplayState(int displayState) { + if (mDisplayState != displayState) { + mDisplayState = displayState; + + for (int i = 0; i < mFeatures.size(); i++) { + mFeatures.get(i).onDisplayStateChanged(displayState == Display.STATE_ON); + } + } + } + + private synchronized void updateMode(int mode) { + for (int i = 0; i < mFeatures.size(); i++) { + mFeatures.get(i).onModeChanged(mode); + } + } + + private synchronized void updateLowPowerMode(boolean lowPowerMode) { + if (mLowPowerMode != lowPowerMode) { + mLowPowerMode = lowPowerMode; + + for (int i = 0; i < mFeatures.size(); i++) { + mFeatures.get(i).onLowPowerModeChanged(mLowPowerMode); + } + } + } + + private final class LiveDisplayHandler extends Handler { + public LiveDisplayHandler(Looper looper) { + super(looper, null, true /*async*/); + } + + @Override + public void handleMessage(Message msg) { + if (!mInitialized) { + return; + } + + switch (msg.what) { + case MSG_DISPLAY_CHANGED: + updateDisplayState(msg.arg1); + break; + case MSG_LOW_POWER_MODE_CHANGED: + updateLowPowerMode(msg.arg1 == 1); + break; + case MSG_TWILIGHT_UPDATE: + updateTwilight(); + nudge(); + break; + case MSG_MODE_CHANGED: + stopNudgingMe(); + updateMode(msg.arg1); + break; + } + } + } +} diff --git a/cm/lib/main/java/org/cyanogenmod/platform/internal/display/OutdoorModeController.java b/cm/lib/main/java/org/cyanogenmod/platform/internal/display/OutdoorModeController.java new file mode 100644 index 0000000..f20979e --- /dev/null +++ b/cm/lib/main/java/org/cyanogenmod/platform/internal/display/OutdoorModeController.java @@ -0,0 +1,217 @@ +/* + * 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 static cyanogenmod.hardware.LiveDisplayManager.MODE_AUTO; +import static cyanogenmod.hardware.LiveDisplayManager.MODE_DAY; +import static cyanogenmod.hardware.LiveDisplayManager.MODE_OUTDOOR; + +import android.content.Context; +import android.net.Uri; +import android.os.Handler; + +import com.android.server.twilight.TwilightState; + +import java.io.PrintWriter; +import java.util.BitSet; + +import cyanogenmod.hardware.CMHardwareManager; +import cyanogenmod.hardware.LiveDisplayManager; +import cyanogenmod.providers.CMSettings; + +public class OutdoorModeController extends LiveDisplayFeature { + + private CMHardwareManager mHardware; + private AmbientLuxObserver mLuxObserver; + + // hardware capabilities + private boolean mUseOutdoorMode; + private boolean mSelfManaged; + + // default values + private int mDefaultOutdoorLux; + private boolean mDefaultAutoOutdoorMode; + + // current values + private boolean mAutoOutdoorMode; + + // internal state + private boolean mIsOutdoor; + private boolean mIsSensorEnabled; + + // sliding window for sensor event smoothing + private static final int SENSOR_WINDOW_MS = 3000; + + public OutdoorModeController(Context context, Handler handler) { + super(context, handler); + } + + @Override + public boolean onStart() { + mHardware = CMHardwareManager.getInstance(mContext); + if (!mHardware.isSupported(CMHardwareManager.FEATURE_SUNLIGHT_ENHANCEMENT)) { + return false; + } + + mUseOutdoorMode = true; + + mDefaultOutdoorLux = mContext.getResources().getInteger( + org.cyanogenmod.platform.internal.R.integer.config_outdoorAmbientLux); + mDefaultAutoOutdoorMode = mContext.getResources().getBoolean( + org.cyanogenmod.platform.internal.R.bool.config_defaultAutoOutdoorMode); + + mSelfManaged = mHardware.isSunlightEnhancementSelfManaged(); + if (!mSelfManaged) { + mLuxObserver = new AmbientLuxObserver(mContext, mHandler.getLooper(), + mDefaultOutdoorLux, SENSOR_WINDOW_MS); + } + + registerSettings( + CMSettings.System.getUriFor(CMSettings.System.DISPLAY_AUTO_OUTDOOR_MODE)); + return true; + } + + @Override + void getCapabilities(final BitSet caps) { + if (mUseOutdoorMode) { + caps.set(LiveDisplayManager.MODE_OUTDOOR); + if (mSelfManaged) { + caps.set(LiveDisplayManager.FEATURE_MANAGED_OUTDOOR_MODE); + } + } + } + + @Override + public void onModeChanged(int mode) { + super.onModeChanged(mode); + updateOutdoorMode(); + } + + @Override + public void onDisplayStateChanged(boolean screenOn) { + super.onDisplayStateChanged(screenOn); + if (mSelfManaged) { + return; + } + updateOutdoorMode(); + } + + @Override + public void onLowPowerModeChanged(boolean lowPowerMode) { + super.onLowPowerModeChanged(lowPowerMode); + updateOutdoorMode(); + } + + @Override + public synchronized void onSettingsChanged(Uri uri) { + mAutoOutdoorMode = getInt(CMSettings.System.DISPLAY_AUTO_OUTDOOR_MODE, + (mDefaultAutoOutdoorMode ? 1 : 0)) == 1; + updateOutdoorMode(); + } + + @Override + public void onTwilightUpdated(TwilightState twilight) { + super.onTwilightUpdated(twilight); + updateOutdoorMode(); + } + + @Override + public void dump(PrintWriter pw) { + pw.println(); + pw.println("OutdoorModeController Configuration:"); + pw.println(" mSelfManaged=" + mSelfManaged); + if (!mSelfManaged) { + pw.println(" mDefaultOutdoorLux=" + mDefaultOutdoorLux); + pw.println(); + pw.println(" OutdoorModeController State:"); + pw.println(" mAutoOutdoorMode=" + mAutoOutdoorMode); + pw.println(" mIsOutdoor=" + mIsOutdoor); + pw.println(" mIsNight=" + isNight()); + } + mLuxObserver.dump(pw); + } + + boolean setAutomaticOutdoorModeEnabled(boolean enabled) { + if (!mUseOutdoorMode) { + return false; + } + putInt(CMSettings.System.DISPLAY_AUTO_OUTDOOR_MODE, (enabled ? 1 : 0)); + return true; + } + + boolean isAutomaticOutdoorModeEnabled() { + return mUseOutdoorMode; + } + + boolean getDefaultAutoOutdoorMode() { + return mDefaultAutoOutdoorMode; + } + + private void observeAmbientLuxLocked(boolean observe) { + mLuxObserver.setTransitionListener(observe ? mListener : null); + } + + /** + * Outdoor mode is optionally enabled when ambient lux > 10000 and it's daytime + * Melt faces! + * + * TODO: Use the camera or RGB sensor to determine if it's really sunlight + */ + private synchronized void updateOutdoorMode() { + /* + * Hardware toggle: + * Enabled if outdoor mode explictly selected + * Enabled if outdoor lux exceeded and day mode or auto mode (if not night) + */ + boolean enabled = !isLowPowerMode() && + (getMode() == MODE_OUTDOOR || + (mAutoOutdoorMode && (mSelfManaged || mIsOutdoor) && + ((getMode() == MODE_AUTO && !isNight()) || getMode() == MODE_DAY))); + mHardware.set(CMHardwareManager.FEATURE_SUNLIGHT_ENHANCEMENT, enabled); + + /* Sensor: + * Enabled in day mode + * Enabled in auto mode if it's not night + * Disabled if outdoor mode explicitly selected + * Disabled in low power mode + * Disabled if screen is off + */ + boolean sensorEnabled = !isLowPowerMode() && isScreenOn() && + getMode() != MODE_OUTDOOR && mAutoOutdoorMode && + ((getMode() == MODE_AUTO && !isNight()) || getMode() == MODE_DAY); + if (mIsSensorEnabled != sensorEnabled) { + mIsSensorEnabled = sensorEnabled; + observeAmbientLuxLocked(sensorEnabled); + } + } + + private final AmbientLuxObserver.TransitionListener mListener = + new AmbientLuxObserver.TransitionListener() { + @Override + public void onTransition(final int state, float ambientLux) { + final boolean outdoor = state == 1; + synchronized (OutdoorModeController.this) { + if (mIsOutdoor == outdoor) { + return; + } + + mIsOutdoor = outdoor; + updateOutdoorMode(); + } + } + }; + +} diff --git a/cm/res/AndroidManifest.xml b/cm/res/AndroidManifest.xml index 3565673..c8fc50a 100644 --- a/cm/res/AndroidManifest.xml +++ b/cm/res/AndroidManifest.xml @@ -219,6 +219,13 @@ android:description="@string/permdesc_weather_access_mgr" android:protectionLevel="normal"/> + + + 250 false - + 6500 4500 9000 + 0 + + false + true + true + true @@ -91,5 +97,6 @@ org.cyanogenmod.platform.internal.IconCacheManagerService org.cyanogenmod.platform.internal.LiveLockScreenServiceBroker org.cyanogenmod.platform.internal.CMWeatherManagerService + org.cyanogenmod.platform.internal.display.LiveDisplayService diff --git a/cm/res/res/values/strings.xml b/cm/res/res/values/strings.xml index 425044d..bc0b22a 100644 --- a/cm/res/res/values/strings.xml +++ b/cm/res/res/values/strings.xml @@ -198,4 +198,9 @@ read data usage database Allows an app to read content from the data usage database. + + + manage livedisplay settings + Allows an app to configure advanced display settings. + diff --git a/cm/res/res/values/symbols.xml b/cm/res/res/values/symbols.xml index 7465ad3..9d52390 100644 --- a/cm/res/res/values/symbols.xml +++ b/cm/res/res/values/symbols.xml @@ -69,6 +69,11 @@ + + + + + diff --git a/sdk/src/java/cyanogenmod/app/CMContextConstants.java b/sdk/src/java/cyanogenmod/app/CMContextConstants.java index 98171b8..07a2980 100644 --- a/sdk/src/java/cyanogenmod/app/CMContextConstants.java +++ b/sdk/src/java/cyanogenmod/app/CMContextConstants.java @@ -131,6 +131,13 @@ public final class CMContextConstants { */ public static final String CM_WEATHER_SERVICE = "cmweather"; + /** + * Manages display color adjustments + * + * @hide + */ + public static final String CM_LIVEDISPLAY_SERVICE = "cmlivedisplay"; + /** * Features supported by the CMSDK. */ @@ -214,5 +221,13 @@ public final class CMContextConstants { */ @SdkConstant(SdkConstant.SdkConstantType.FEATURE) public static final String WEATHER_SERVICES = "org.cyanogenmod.weather"; + + /** + * Feature for {@link PackageManager#getSystemAvailableFeatures} and + * {@link PackageManager#hasSystemFeature}: The device includes the LiveDisplay service + * utilized by the cmsdk. + */ + @SdkConstant(SdkConstant.SdkConstantType.FEATURE) + public static final String LIVEDISPLAY = "org.cyanogenmod.livedisplay"; } } diff --git a/sdk/src/java/cyanogenmod/hardware/ILiveDisplayService.aidl b/sdk/src/java/cyanogenmod/hardware/ILiveDisplayService.aidl new file mode 100644 index 0000000..68e2b86 --- /dev/null +++ b/sdk/src/java/cyanogenmod/hardware/ILiveDisplayService.aidl @@ -0,0 +1,50 @@ +/** + * 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 cyanogenmod.hardware; + +import cyanogenmod.hardware.LiveDisplayConfig; + +/** @hide */ +interface ILiveDisplayService { + LiveDisplayConfig getConfig(); + + int getMode(); + boolean setMode(int mode); + + float[] getColorAdjustment(); + boolean setColorAdjustment(in float[] adj); + + boolean isAutoContrastEnabled(); + boolean setAutoContrastEnabled(boolean enabled); + + boolean isCABCEnabled(); + boolean setCABCEnabled(boolean enabled); + + boolean isColorEnhancementEnabled(); + boolean setColorEnhancementEnabled(boolean enabled); + + int getDayColorTemperature(); + boolean setDayColorTemperature(int temperature); + + int getNightColorTemperature(); + boolean setNightColorTemperature(int temperature); + + int getColorTemperature(); + + boolean isAutomaticOutdoorModeEnabled(); + boolean setAutomaticOutdoorModeEnabled(boolean enabled); +} diff --git a/sdk/src/java/cyanogenmod/hardware/LiveDisplayConfig.aidl b/sdk/src/java/cyanogenmod/hardware/LiveDisplayConfig.aidl new file mode 100644 index 0000000..326e298 --- /dev/null +++ b/sdk/src/java/cyanogenmod/hardware/LiveDisplayConfig.aidl @@ -0,0 +1,19 @@ +/* + * 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 cyanogenmod.hardware; + +parcelable LiveDisplayConfig; diff --git a/sdk/src/java/cyanogenmod/hardware/LiveDisplayConfig.java b/sdk/src/java/cyanogenmod/hardware/LiveDisplayConfig.java new file mode 100644 index 0000000..0a569d3 --- /dev/null +++ b/sdk/src/java/cyanogenmod/hardware/LiveDisplayConfig.java @@ -0,0 +1,244 @@ +/* + * 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 cyanogenmod.hardware; + +import static cyanogenmod.hardware.LiveDisplayManager.FEATURE_FIRST; +import static cyanogenmod.hardware.LiveDisplayManager.FEATURE_LAST; +import static cyanogenmod.hardware.LiveDisplayManager.MODE_FIRST; +import static cyanogenmod.hardware.LiveDisplayManager.MODE_LAST; +import static cyanogenmod.hardware.LiveDisplayManager.MODE_OFF; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.BitSet; + +import cyanogenmod.os.Build; +import cyanogenmod.os.Concierge; +import cyanogenmod.os.Concierge.ParcelInfo; + +/** + * Holder class for LiveDisplay static configuration. + * + * This class holds various defaults and hardware capabilities + * which are involved with LiveDisplay. + */ +public class LiveDisplayConfig implements Parcelable { + + private final BitSet mCapabilities; + private final int mDefaultDayTemperature; + private final int mDefaultNightTemperature; + private final int mDefaultMode; + + private final boolean mDefaultAutoContrast; + private final boolean mDefaultAutoOutdoorMode; + private final boolean mDefaultCABC; + private final boolean mDefaultColorEnhancement; + + public LiveDisplayConfig(BitSet capabilities, int defaultMode, + int defaultDayTemperature, int defaultNightTemperature, + boolean defaultAutoOutdoorMode, boolean defaultAutoContrast, + boolean defaultCABC, boolean defaultColorEnhancement) { + super(); + mCapabilities = (BitSet) capabilities.clone(); + mDefaultMode = defaultMode; + mDefaultDayTemperature = defaultDayTemperature; + mDefaultNightTemperature = defaultNightTemperature; + mDefaultAutoContrast = defaultAutoContrast; + mDefaultAutoOutdoorMode = defaultAutoOutdoorMode; + mDefaultCABC = defaultCABC; + mDefaultColorEnhancement = defaultColorEnhancement; + } + + private LiveDisplayConfig(Parcel parcel) { + // Read parcelable version via the Concierge + ParcelInfo parcelInfo = Concierge.receiveParcel(parcel); + int parcelableVersion = parcelInfo.getParcelVersion(); + + // temp vars + long capabilities = 0; + int defaultMode = 0; + int defaultDayTemperature = -1; + int defaultNightTemperature = -1; + boolean defaultAutoContrast = false; + boolean defaultAutoOutdoorMode = false; + boolean defaultCABC = false; + boolean defaultColorEnhancement = false; + + if (parcelableVersion >= Build.CM_VERSION_CODES.FIG) { + capabilities = parcel.readLong(); + defaultMode = parcel.readInt(); + defaultDayTemperature = parcel.readInt(); + defaultNightTemperature = parcel.readInt(); + defaultAutoContrast = parcel.readInt() == 1; + defaultAutoOutdoorMode = parcel.readInt() == 1; + defaultCABC = parcel.readInt() == 1; + defaultColorEnhancement = parcel.readInt() == 1; + } + + // set temps + mCapabilities = BitSet.valueOf(new long[] { capabilities }); + mDefaultMode = defaultMode; + mDefaultDayTemperature = defaultDayTemperature; + mDefaultNightTemperature = defaultNightTemperature; + mDefaultAutoContrast = defaultAutoContrast; + mDefaultAutoOutdoorMode = defaultAutoOutdoorMode; + mDefaultCABC = defaultCABC; + mDefaultColorEnhancement = defaultColorEnhancement; + + // Complete parcel info for the concierge + parcelInfo.complete(); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("capabilities=").append(mCapabilities.toString()); + sb.append(" defaultMode=").append(mDefaultMode); + sb.append(" defaultDayTemperature=").append(mDefaultDayTemperature); + sb.append(" defaultNightTemperature=").append(mDefaultNightTemperature); + sb.append(" defaultAutoOutdoorMode=").append(mDefaultAutoOutdoorMode); + sb.append(" defaultAutoContrast=").append(mDefaultAutoContrast); + sb.append(" defaultCABC=").append(mDefaultCABC); + sb.append(" defaultColorEnhancement=").append(mDefaultColorEnhancement); + return sb.toString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + // Tell the concierge to prepare the parcel + ParcelInfo parcelInfo = Concierge.prepareParcel(out); + + // ==== FIG ===== + out.writeLong(mCapabilities.toLongArray()[0]); + out.writeInt(mDefaultMode); + out.writeInt(mDefaultDayTemperature); + out.writeInt(mDefaultNightTemperature); + out.writeInt(mDefaultAutoContrast ? 1 : 0); + out.writeInt(mDefaultAutoOutdoorMode ? 1 : 0); + out.writeInt(mDefaultCABC ? 1 : 0); + out.writeInt(mDefaultColorEnhancement ? 1 : 0); + + // Complete the parcel info for the concierge + parcelInfo.complete(); + } + + /** + * Checks if a particular feature or mode is supported by the system. + * + * @param feature + * @return true if capable + */ + public boolean hasFeature(int feature) { + return ((feature >= MODE_FIRST && feature <= MODE_LAST) || + (feature >= FEATURE_FIRST && feature <= FEATURE_LAST)) && + (feature == MODE_OFF || mCapabilities.get(feature)); + } + + /** + * Checks if LiveDisplay is available for use on this device. + * + * @return true if any feature is enabled + */ + public boolean isAvailable() { + return !mCapabilities.isEmpty(); + } + + /** + * Gets the default color temperature to use in the daytime. This is typically + * set to 6500K, however this may not be entirely accurate. Use this value for + * resetting controls to the default. + * + * @return the default day temperature in K + */ + public int getDefaultDayTemperature() { + return mDefaultDayTemperature; + } + + /** + * Gets the default color temperature to use at night. This is typically set + * to 4500K, but this may not be entirely accurate. Use this value for resetting + * controls to defaults. + * + * @return the default night color temperature + */ + public int getDefaultNightTemperature() { + return mDefaultNightTemperature; + } + + /** + * Get the default adaptive mode. + * + * @return the default mode + */ + public int getDefaultMode() { + return mDefaultMode; + } + + /** + * Get the default value for auto contrast + * + * @return true if enabled + */ + public boolean getDefaultAutoContrast() { + return mDefaultAutoContrast; + } + + /** + * Get the default value for automatic outdoor mode + * + * @return true if enabled + */ + public boolean getDefaultAutoOutdoorMode() { + return mDefaultAutoOutdoorMode; + } + + /** + * Get the default value for CABC + * + * @return true if enabled + */ + public boolean getDefaultCABC() { + return mDefaultCABC; + } + + /** + * Get the default value for color enhancement + * + * @return true if enabled + */ + public boolean getDefaultColorEnhancement() { + return mDefaultColorEnhancement; + } + + /** @hide */ + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public LiveDisplayConfig createFromParcel(Parcel in) { + return new LiveDisplayConfig(in); + } + + @Override + public LiveDisplayConfig[] newArray(int size) { + return new LiveDisplayConfig[size]; + } + }; +} diff --git a/sdk/src/java/cyanogenmod/hardware/LiveDisplayManager.java b/sdk/src/java/cyanogenmod/hardware/LiveDisplayManager.java new file mode 100644 index 0000000..0e4f7a1 --- /dev/null +++ b/sdk/src/java/cyanogenmod/hardware/LiveDisplayManager.java @@ -0,0 +1,415 @@ +/* + * 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 cyanogenmod.hardware; + +import android.content.Context; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; + +import cyanogenmod.app.CMContextConstants; + +/** + * LiveDisplay is an advanced set of features for improving + * display quality under various ambient conditions. + * + * The backend service is constructed with a set of LiveDisplayFeatures + * which provide capabilities such as outdoor mode, night mode, + * and calibration. It interacts with CMHardwareService to relay + * changes down to the lower layers. + * + * Multiple adaptive modes are supported, and various hardware + * features such as CABC, ACO and color enhancement are also + * managed by LiveDisplay. + */ +public class LiveDisplayManager { + + /** + * Disable all LiveDisplay adaptive features + */ + public static final int MODE_OFF = 0; + + /** + * Change color temperature to night mode + */ + public static final int MODE_NIGHT = 1; + + /** + * Enable automatic detection of appropriate mode + */ + public static final int MODE_AUTO = 2; + + /** + * Increase brightness/contrast/saturation for sunlight + */ + public static final int MODE_OUTDOOR = 3; + + /** + * Change color temperature to day mode, and allow + * detection of outdoor conditions + */ + public static final int MODE_DAY = 4; + + /** @hide */ + public static final int MODE_FIRST = MODE_OFF; + /** @hide */ + public static final int MODE_LAST = MODE_DAY; + + /** + * Content adaptive backlight control, adjust images to + * increase brightness in order to reduce backlight level + */ + public static final int FEATURE_CABC = 10; + + /** + * Adjust images to increase contrast + */ + public static final int FEATURE_AUTO_CONTRAST = 11; + + /** + * Adjust image to improve saturation and color + */ + public static final int FEATURE_COLOR_ENHANCEMENT = 12; + + /** + * Capable of adjusting RGB levels + */ + public static final int FEATURE_COLOR_ADJUSTMENT = 13; + + /** + * System supports outdoor mode, but environmental sensing + * is done by an external application. + */ + public static final int FEATURE_MANAGED_OUTDOOR_MODE = 14; + + /** + * System supports multiple display calibrations + * for different viewing intents. + */ + public static final int FEATURE_DISPLAY_MODES = 15; + + /** @hide */ + public static final int FEATURE_FIRST = FEATURE_CABC; + /** @hide */ + public static final int FEATURE_LAST = FEATURE_DISPLAY_MODES; + + private static final String TAG = "LiveDisplay"; + + private final Context mContext; + private final LiveDisplayConfig mConfig; + + private static LiveDisplayManager sInstance; + private static ILiveDisplayService sService; + + /** + * @hide to prevent subclassing from outside of the framework + */ + private LiveDisplayManager(Context context) { + Context appContext = context.getApplicationContext(); + if (appContext != null) { + mContext = appContext; + } else { + mContext = context; + } + sService = getService(); + + if (context.getPackageManager().hasSystemFeature( + CMContextConstants.Features.LIVEDISPLAY) && !checkService()) { + throw new RuntimeException("Unable to get LiveDisplayService. The service either" + + " crashed, was not started, or the interface has been called to early in" + + " SystemServer init"); + } + + try { + mConfig = sService.getConfig(); + } catch (RemoteException e) { + throw new RuntimeException("Unable to fetch LiveDisplay configuration!", e); + } + } + + /** + * Get or create an instance of the {@link cyanogenmod.hardware.LiveDisplayManager} + * @param context + * @return {@link LiveDisplayManager} + */ + public synchronized static LiveDisplayManager getInstance(Context context) { + if (sInstance == null) { + sInstance = new LiveDisplayManager(context); + } + return sInstance; + } + + /** @hide */ + public static ILiveDisplayService getService() { + if (sService != null) { + return sService; + } + IBinder b = ServiceManager.getService(CMContextConstants.CM_LIVEDISPLAY_SERVICE); + if (b != null) { + sService = ILiveDisplayService.Stub.asInterface(b); + return sService; + } + return null; + } + + /** + * @return true if service is valid + */ + private boolean checkService() { + if (sService == null) { + Log.w(TAG, "not connected to CMHardwareManagerService"); + return false; + } + return true; + } + + /** + * Gets the static configuration and settings. + * + * @return the configuration + */ + public LiveDisplayConfig getConfig() { + return mConfig; + } + + /** + * Returns the current adaptive mode. + * + * @return id of the selected mode + */ + public int getMode() { + try { + return checkService() ? sService.getMode() : MODE_OFF; + } catch (RemoteException e) { + return MODE_OFF; + } + } + + /** + * Selects a new adaptive mode. + * + * @param mode + * @return true if the mode was selected + */ + public boolean setMode(int mode) { + try { + return checkService() && sService.setMode(mode); + } catch (RemoteException e) { + return false; + } + } + + /** + * Checks if the auto contrast optimization feature is enabled. + * + * @return true if enabled + */ + public boolean isAutoContrastEnabled() { + try { + return checkService() && sService.isAutoContrastEnabled(); + } catch (RemoteException e) { + return false; + } + } + + /** + * Sets the state of auto contrast optimization + * + * @param enabled + * @return true if state was changed + */ + public boolean setAutoContrastEnabled(boolean enabled) { + try { + return checkService() && sService.setAutoContrastEnabled(enabled); + } catch (RemoteException e) { + return false; + } + } + + /** + * Checks if the CABC feature is enabled + * + * @return true if enabled + */ + public boolean isCABCEnabled() { + try { + return checkService() && sService.isCABCEnabled(); + } catch (RemoteException e) { + return false; + } + } + + /** + * Sets the state of CABC + * + * @param enabled + * @return true if state was changed + */ + public boolean setCABCEnabled(boolean enabled) { + try { + return checkService() && sService.setCABCEnabled(enabled); + } catch (RemoteException e) { + return false; + } + } + + /** + * Checks if the color enhancement feature is enabled + * + * @return true if enabled + */ + public boolean isColorEnhancementEnabled() { + try { + return checkService() && sService.isColorEnhancementEnabled(); + } catch (RemoteException e) { + return false; + } + } + + /** + * Sets the state of color enhancement + * + * @param enabled + * @return true if state was changed + */ + public boolean setColorEnhancementEnabled(boolean enabled) { + try { + return checkService() && sService.setColorEnhancementEnabled(enabled); + } catch (RemoteException e) { + return false; + } + } + + /** + * Gets the user-specified color temperature to use in the daytime. + * + * @return the day color temperature + */ + public int getDayColorTemperature() { + try { + return checkService() ? sService.getDayColorTemperature() : -1; + } catch (RemoteException e) { + return -1; + } + } + + /** + * Sets the color temperature to use in the daytime. + * + * @param temperature + * @return true if state was changed + */ + public boolean setDayColorTemperature(int temperature) { + try { + return checkService() && sService.setDayColorTemperature(temperature); + } catch (RemoteException e) { + return false; + } + } + + /** + * Gets the user-specified color temperature to use at night. + * + * @return the night color temperature + */ + public int getNightColorTemperature() { + try { + return checkService() ? sService.getNightColorTemperature() : -1; + } catch (RemoteException e) { + return -1; + } + } + + /** + * Sets the color temperature to use at night. + * + * @param temperature + * @return true if state was changed + */ + public boolean setNightColorTemperature(int temperature) { + try { + return checkService() && sService.setNightColorTemperature(temperature); + } catch (RemoteException e) { + return false; + } + } + + /** + * Checks if outdoor mode should be enabled automatically when under extremely high + * ambient light. This is typically around 12000 lux. + * + * @return if outdoor conditions should be detected + */ + public boolean isAutomaticOutdoorModeEnabled() { + try { + return checkService() && sService.isAutomaticOutdoorModeEnabled(); + } catch (RemoteException e) { + return false; + } + } + + /** + * Enables automatic detection of outdoor conditions. Outdoor mode is triggered + * when high ambient light is detected and it's not night. + * + * @param enabled + * @return true if state was changed + */ + public boolean setAutomaticOutdoorModeEnabled(boolean enabled) { + try { + return checkService() && sService.setAutomaticOutdoorModeEnabled(enabled); + } catch (RemoteException e) { + return false; + } + } + + /** + * Gets the current RGB triplet which is applied as a color adjustment. + * The values are floats between 0 and 1. A setting of { 1.0, 1.0, 1.0 } + * means that no adjustment is made. + * + * @return array of { R, G, B } offsets + */ + public float[] getColorAdjustment() { + try { + if (checkService()) { + return sService.getColorAdjustment(); + } + } catch (RemoteException e) { + } + return new float[] { 1.0f, 1.0f, 1.0f }; + } + + /** + * Sets the color adjustment to use. This can be set by the user to calibrate + * their screen. This should be sent in the format { R, G, B } as floats from + * 0 to 1. A setting of { 1.0, 1.0, 1.0 } means that no adjustment is made. + * The hardware implementation may refuse certain values which make the display + * unreadable, such as { 0, 0, 0 }. This calibration will be combined with other + * internal adjustments, such as night mode, if necessary. + * + * @param array of { R, G, B } offsets + * @return true if state was changed + */ + public boolean setColorAdjustment(float[] adj) { + try { + return checkService() && sService.setColorAdjustment(adj); + } catch (RemoteException e) { + return false; + } + } +} diff --git a/sdk/src/java/cyanogenmod/providers/CMSettings.java b/sdk/src/java/cyanogenmod/providers/CMSettings.java index 47cc524..2b3b07a 100644 --- a/sdk/src/java/cyanogenmod/providers/CMSettings.java +++ b/sdk/src/java/cyanogenmod/providers/CMSettings.java @@ -68,6 +68,16 @@ public final class CMSettings { */ public static final String ACTION_DATA_USAGE = "cyanogenmod.settings.ACTION_DATA_USAGE"; + /** + * Activity Action: Show LiveDisplay settings + *

+ * Input: Nothing. + *

+ * Output: Nothing. + */ + public static final String ACTION_LIVEDISPLAY_SETTINGS = + "cyanogenmod.settings.LIVEDISPLAY_SETTINGS"; + // region Call Methods /** @@ -1318,10 +1328,15 @@ public final class CMSettings { * Use display power saving features such as CABC or CABL * 0 = 0ff, 1 = on */ - public static final String DISPLAY_LOW_POWER = "display_low_power"; + public static final String DISPLAY_CABC = "display_low_power"; + + /** + * @deprecated + */ + public static final String DISPLAY_LOW_POWER = DISPLAY_CABC; /** @hide */ - public static final Validator DISPLAY_LOW_POWER_VALIDATOR = + public static final Validator DISPLAY_CABC_VALIDATOR = sBooleanValidator; /** @@ -1334,6 +1349,16 @@ public final class CMSettings { public static final Validator DISPLAY_COLOR_ENHANCE_VALIDATOR = sBooleanValidator; + /** + * Use auto contrast optimization feature of display + * 0 = 0ff, 1 = on + */ + public static final String DISPLAY_AUTO_CONTRAST = "display_auto_contrast"; + + /** @hide */ + public static final Validator DISPLAY_AUTO_CONTRAST_VALIDATOR = + sBooleanValidator; + /** * Manual display color adjustments (RGB values as floats, separated by spaces) */ @@ -1827,7 +1852,7 @@ public final class CMSettings { CMSettings.System.DISPLAY_TEMPERATURE_NIGHT, CMSettings.System.DISPLAY_TEMPERATURE_MODE, CMSettings.System.DISPLAY_AUTO_OUTDOOR_MODE, - CMSettings.System.DISPLAY_LOW_POWER, + CMSettings.System.DISPLAY_CABC, CMSettings.System.DISPLAY_COLOR_ENHANCE, CMSettings.System.DISPLAY_COLOR_ADJUSTMENT, CMSettings.System.LIVE_DISPLAY_HINTED, @@ -1965,8 +1990,9 @@ public final class CMSettings { VALIDATORS.put(DISPLAY_TEMPERATURE_DAY, DISPLAY_TEMPERATURE_DAY_VALIDATOR); VALIDATORS.put(DISPLAY_TEMPERATURE_NIGHT, DISPLAY_TEMPERATURE_NIGHT_VALIDATOR); VALIDATORS.put(DISPLAY_TEMPERATURE_MODE, DISPLAY_TEMPERATURE_MODE_VALIDATOR); + VALIDATORS.put(DISPLAY_AUTO_CONTRAST, DISPLAY_AUTO_CONTRAST_VALIDATOR); VALIDATORS.put(DISPLAY_AUTO_OUTDOOR_MODE, DISPLAY_AUTO_OUTDOOR_MODE_VALIDATOR); - VALIDATORS.put(DISPLAY_LOW_POWER, DISPLAY_LOW_POWER_VALIDATOR); + VALIDATORS.put(DISPLAY_CABC, DISPLAY_CABC_VALIDATOR); VALIDATORS.put(DISPLAY_COLOR_ENHANCE, DISPLAY_COLOR_ENHANCE_VALIDATOR); VALIDATORS.put(DISPLAY_COLOR_ADJUSTMENT, DISPLAY_COLOR_ADJUSTMENT_VALIDATOR); VALIDATORS.put(LIVE_DISPLAY_HINTED, LIVE_DISPLAY_HINTED_VALIDATOR); diff --git a/system-api/cm_system-current.txt b/system-api/cm_system-current.txt index ff84505..5992f3f 100644 --- a/system-api/cm_system-current.txt +++ b/system-api/cm_system-current.txt @@ -529,6 +529,53 @@ package cyanogenmod.hardware { method public boolean onTransact(int, android.os.Parcel, android.os.Parcel, int) throws android.os.RemoteException; } + public class LiveDisplayConfig implements android.os.Parcelable { + ctor public LiveDisplayConfig(java.util.BitSet, int, int, int, boolean, boolean, boolean, boolean); + method public int describeContents(); + method public boolean getDefaultAutoContrast(); + method public boolean getDefaultAutoOutdoorMode(); + method public boolean getDefaultCABC(); + method public boolean getDefaultColorEnhancement(); + method public int getDefaultDayTemperature(); + method public int getDefaultMode(); + method public int getDefaultNightTemperature(); + method public boolean hasFeature(int); + method public boolean isAvailable(); + method public void writeToParcel(android.os.Parcel, int); + } + + public class LiveDisplayManager { + method public float[] getColorAdjustment(); + method public cyanogenmod.hardware.LiveDisplayConfig getConfig(); + method public int getDayColorTemperature(); + method public static synchronized cyanogenmod.hardware.LiveDisplayManager getInstance(android.content.Context); + method public int getMode(); + method public int getNightColorTemperature(); + method public boolean isAutoContrastEnabled(); + method public boolean isAutomaticOutdoorModeEnabled(); + method public boolean isCABCEnabled(); + method public boolean isColorEnhancementEnabled(); + method public boolean setAutoContrastEnabled(boolean); + method public boolean setAutomaticOutdoorModeEnabled(boolean); + method public boolean setCABCEnabled(boolean); + method public boolean setColorAdjustment(float[]); + method public boolean setColorEnhancementEnabled(boolean); + method public boolean setDayColorTemperature(int); + method public boolean setMode(int); + method public boolean setNightColorTemperature(int); + field public static final int FEATURE_AUTO_CONTRAST = 11; // 0xb + field public static final int FEATURE_CABC = 10; // 0xa + field public static final int FEATURE_COLOR_ADJUSTMENT = 13; // 0xd + field public static final int FEATURE_COLOR_ENHANCEMENT = 12; // 0xc + field public static final int FEATURE_DISPLAY_MODES = 15; // 0xf + field public static final int FEATURE_MANAGED_OUTDOOR_MODE = 14; // 0xe + field public static final int MODE_AUTO = 2; // 0x2 + field public static final int MODE_DAY = 4; // 0x4 + field public static final int MODE_NIGHT = 1; // 0x1 + field public static final int MODE_OFF = 0; // 0x0 + field public static final int MODE_OUTDOOR = 3; // 0x3 + } + public abstract class ThermalListenerCallback extends cyanogenmod.hardware.IThermalListenerCallback.Stub { ctor public ThermalListenerCallback(); } @@ -613,6 +660,7 @@ package cyanogenmod.platform { field public static final java.lang.String HARDWARE_ABSTRACTION_ACCESS = "cyanogenmod.permission.HARDWARE_ABSTRACTION_ACCESS"; field public static final java.lang.String LIVE_LOCK_SCREEN_MANAGER_ACCESS = "cyanogenmod.permission.LIVE_LOCK_SCREEN_MANAGER_ACCESS"; field public static final java.lang.String MANAGE_ALARMS = "cyanogenmod.permission.MANAGE_ALARMS"; + field public static final java.lang.String MANAGE_LIVEDISPLAY = "cyanogenmod.permission.MANAGE_LIVEDISPLAY"; field public static final java.lang.String MANAGE_PERSISTENT_STORAGE = "cyanogenmod.permission.MANAGE_PERSISTENT_STORAGE"; field public static final java.lang.String MODIFY_MSIM_PHONE_STATE = "cyanogenmod.permission.MODIFY_MSIM_PHONE_STATE"; field public static final java.lang.String MODIFY_NETWORK_SETTINGS = "cyanogenmod.permission.MODIFY_NETWORK_SETTINGS"; @@ -795,6 +843,7 @@ package cyanogenmod.providers { public final class CMSettings { ctor public CMSettings(); field public static final java.lang.String ACTION_DATA_USAGE = "cyanogenmod.settings.ACTION_DATA_USAGE"; + field public static final java.lang.String ACTION_LIVEDISPLAY_SETTINGS = "cyanogenmod.settings.LIVEDISPLAY_SETTINGS"; field public static final java.lang.String AUTHORITY = "cmsettings"; } @@ -868,10 +917,12 @@ package cyanogenmod.providers { field public static final android.net.Uri CONTENT_URI; field public static final java.lang.String DIALER_OPENCNAM_ACCOUNT_SID = "dialer_opencnam_account_sid"; field public static final java.lang.String DIALER_OPENCNAM_AUTH_TOKEN = "dialer_opencnam_auth_token"; + field public static final java.lang.String DISPLAY_AUTO_CONTRAST = "display_auto_contrast"; field public static final java.lang.String DISPLAY_AUTO_OUTDOOR_MODE = "display_auto_outdoor_mode"; + field public static final java.lang.String DISPLAY_CABC = "display_low_power"; field public static final java.lang.String DISPLAY_COLOR_ADJUSTMENT = "display_color_adjustment"; field public static final java.lang.String DISPLAY_COLOR_ENHANCE = "display_color_enhance"; - field public static final java.lang.String DISPLAY_LOW_POWER = "display_low_power"; + field public static final deprecated java.lang.String DISPLAY_LOW_POWER = "display_low_power"; field public static final java.lang.String DISPLAY_TEMPERATURE_DAY = "display_temperature_day"; field public static final java.lang.String DISPLAY_TEMPERATURE_MODE = "display_temperature_mode"; field public static final java.lang.String DISPLAY_TEMPERATURE_NIGHT = "display_temperature_night";