diff options
author | Steve Kondik <steve@cyngn.com> | 2016-03-31 11:23:36 -0700 |
---|---|---|
committer | Steve Kondik <shade@chemlab.org> | 2016-04-13 01:37:08 -0700 |
commit | 1dab5a0ca90d877c2f1728e75176e100547192ea (patch) | |
tree | f71cc31de251eaf59b5ee307113142667595f3d0 /cm | |
parent | 620b1eb9084c2ea1d15ab20946f4c391588135c2 (diff) | |
download | vendor_cmsdk-1dab5a0ca90d877c2f1728e75176e100547192ea.zip vendor_cmsdk-1dab5a0ca90d877c2f1728e75176e100547192ea.tar.gz vendor_cmsdk-1dab5a0ca90d877c2f1728e75176e100547192ea.tar.bz2 |
cmsdk: Refactoring LiveDisplay
* Moving LiveDisplay to CMSDK!
* Completely redesigned the feature for future expansion.
* No new features in this patch, but a proper API is being
designed.
Change-Id: Ic8f55678f9141bf3386b2a1cf2fd1e8b3916c278
Diffstat (limited to 'cm')
10 files changed, 1884 insertions, 1 deletions
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<Sample> mRing = new LinkedList<Sample>(); + + 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<Sample> 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<Uri> settings = new ArrayList<Uri>(); + + 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<LiveDisplayFeature> mFeatures = new ArrayList<LiveDisplayFeature>(); + + 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<LiveDisplayFeature> 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"/> + <!-- Allows an application to manage LiveDisplay --> + <permission android:name="cyanogenmod.permission.MANAGE_LIVEDISPLAY" + android:label="@string/permlab_manageLiveDisplay" + android:description="@string/permdesc_manageLiveDisplay" + android:icon="@drawable/ic_launcher_cyanogenmod" + android:protectionLevel="normal" /> + <application android:process="system" android:persistent="true" android:hasCode="false" diff --git a/cm/res/res/values/config.xml b/cm/res/res/values/config.xml index 3a5b7d2..3755e4b 100644 --- a/cm/res/res/values/config.xml +++ b/cm/res/res/values/config.xml @@ -51,10 +51,16 @@ <integer name="config_proximityCheckTimeout">250</integer> <bool name="config_proximityCheckOnWakeEnabledByDefault">false</bool> - <!-- Default values for display color temperature --> + <!-- Default values for LiveDisplay --> <integer name="config_dayColorTemperature">6500</integer> <integer name="config_nightColorTemperature">4500</integer> <integer name="config_outdoorAmbientLux">9000</integer> + <integer name="config_defaultLiveDisplayMode">0</integer> + + <bool name="config_defaultAutoContrast">false</bool> + <bool name="config_defaultAutoOutdoorMode">true</bool> + <bool name="config_defaultColorEnhancement">true</bool> + <bool name="config_defaultCABC">true</bool> <!-- Is the notification LED brightness adjustable ? Used to decide if the user can set LED brightness --> @@ -91,5 +97,6 @@ <item>org.cyanogenmod.platform.internal.IconCacheManagerService</item> <item>org.cyanogenmod.platform.internal.LiveLockScreenServiceBroker</item> <item>org.cyanogenmod.platform.internal.CMWeatherManagerService</item> + <item>org.cyanogenmod.platform.internal.display.LiveDisplayService</item> </string-array> </resources> 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 @@ <string name="permlab_dataUsageRead">read data usage database</string> <!-- DataUsageProvider read permission description --> <string name="permdesc_dataUsageRead">Allows an app to read content from the data usage database.</string> + + <!-- LiveDisplay manager permission --> + <string name="permlab_manageLiveDisplay">manage livedisplay settings</string> + <string name="permdesc_manageLiveDisplay">Allows an app to configure advanced display settings.</string> + </resources> 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 @@ <java-symbol type="integer" name="config_dayColorTemperature" /> <java-symbol type="integer" name="config_nightColorTemperature" /> <java-symbol type="integer" name="config_outdoorAmbientLux" /> + <java-symbol type="integer" name="config_defaultLiveDisplayMode" /> + <java-symbol type="bool" name="config_defaultAutoContrast" /> + <java-symbol type="bool" name="config_defaultAutoOutdoorMode" /> + <java-symbol type="bool" name="config_defaultColorEnhancement" /> + <java-symbol type="bool" name="config_defaultCABC" /> <!-- Notification and battery light --> <java-symbol type="bool" name="config_adjustableNotificationLedBrightness" /> |