From 89f71f314e452a254c5ccc44a026facb3f1254b0 Mon Sep 17 00:00:00 2001 From: shagbag913 Date: Sun, 22 Dec 2019 05:53:41 -0500 Subject: [PATCH 01/22] KeyguardSliceView: adjust placement when Type clock is in use Change-Id: I401e3c666d905b79047ac20edae283bd91021ff5 --- packages/SystemUI/res-keyguard/values/dimens.xml | 3 +++ .../com/android/keyguard/KeyguardClockSwitch.java | 13 ++++++++++++- .../src/com/android/keyguard/KeyguardSliceView.java | 8 ++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index f9389ce24d96..9bad1654573f 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -81,4 +81,7 @@ -32dp + + + 32dp diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 3af445e9a2ae..368b1fdcf30f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -18,6 +18,7 @@ import android.util.AttributeSet; import android.util.Log; import android.util.MathUtils; +import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -29,6 +30,7 @@ import com.android.internal.colorextraction.ColorExtractor; import com.android.internal.colorextraction.ColorExtractor.OnColorsChangedListener; import com.android.keyguard.clock.ClockManager; +import com.android.keyguard.KeyguardSliceView; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.colorextraction.SysuiColorExtractor; @@ -111,7 +113,7 @@ public class KeyguardClockSwitch extends RelativeLayout { * Status area (date and other stuff) shown below the clock. Plugin can decide whether or not to * show it below the alternate clock. */ - private View mKeyguardStatusArea; + private KeyguardSliceView mKeyguardStatusArea; /** * Maintain state so that a newly connected plugin can be initialized. @@ -238,6 +240,7 @@ private void setClockPlugin(ClockPlugin plugin) { mClockPlugin.onDestroyView(); mClockPlugin = null; } + adjustStatusAreaPadding(plugin); if (plugin == null) { if (mShowingHeader) { mClockView.setVisibility(View.GONE); @@ -470,6 +473,14 @@ private void updateBigClockAlpha() { } } + private void adjustStatusAreaPadding(ClockPlugin plugin) { + final boolean mIsTypeClock = plugin != null && plugin.getName().equals("type"); + mKeyguardStatusArea.setRowGravity(mIsTypeClock ? Gravity.LEFT : Gravity.CENTER); + mKeyguardStatusArea.setRowPadding(mIsTypeClock ? mContext.getResources() + .getDimensionPixelSize(R.dimen.keyguard_status_area_typeclock_padding) : 0, 0, 0, + 0); + } + /** * Sets if the keyguard slice is showing a center-aligned header. We need a smaller clock in * these cases. diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index d35b8ba0d4f0..650cdf7745c2 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -192,6 +192,14 @@ public void onVisibilityAggregated(boolean isVisible) { setLayoutTransition(isVisible ? mLayoutTransition : null); } + public void setRowGravity(int gravity) { + mRow.setGravity(gravity); + } + + public void setRowPadding(int left, int top, int right, int bottom) { + mRow.setPadding(left, top, right, bottom); + } + /** * Returns whether the current visible slice has a title/header. */ From e396bfd43258c6bea6b0cc675126141a544802a6 Mon Sep 17 00:00:00 2001 From: Lucchetto Date: Sat, 14 Nov 2020 18:36:17 +0100 Subject: [PATCH 02/22] Revert "TelephonyManager: make duplicated baseband string hax configurable" This reverts commit 8cea738bb98e36a9f631d4e1effcf71004506e6a. Signed-off-by: Lucchetto --- core/res/res-revengeos/values/config.xml | 3 --- core/res/res-revengeos/values/symbols.xml | 2 -- telephony/java/android/telephony/TelephonyManager.java | 8 ++------ 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/core/res/res-revengeos/values/config.xml b/core/res/res-revengeos/values/config.xml index 9f307c43ee5e..ede3e94fa06d 100644 --- a/core/res/res-revengeos/values/config.xml +++ b/core/res/res-revengeos/values/config.xml @@ -125,9 +125,6 @@ true - - false - diff --git a/core/res/res-revengeos/values/symbols.xml b/core/res/res-revengeos/values/symbols.xml index 6251abf9f8f0..7acdc2d7d92f 100644 --- a/core/res/res-revengeos/values/symbols.xml +++ b/core/res/res-revengeos/values/symbols.xml @@ -53,8 +53,6 @@ - - diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 3f59cc6f1de3..fbd5317e69ed 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -10103,12 +10103,8 @@ public void setBasebandVersion(String version) { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public void setBasebandVersionForPhone(int phoneId, String version) { if (SubscriptionManager.isValidPhoneId(phoneId)) { - List newList = updateTelephonyProperty( - TelephonyProperties.baseband_version(), phoneId, version); - if (mContext.getResources().getBoolean( - com.android.internal.R.bool.config_trim_baseband_version_string)) { - newList = Arrays.asList(newList.get(0)); - } + List newList = Arrays.asList(updateTelephonyProperty( + TelephonyProperties.baseband_version(), phoneId, version).get(0)); TelephonyProperties.baseband_version(newList); } } From 8d8b5b841cfbaf69819ade9edaa19911a21e5d88 Mon Sep 17 00:00:00 2001 From: Lucchetto Date: Sat, 14 Nov 2020 18:36:41 +0100 Subject: [PATCH 03/22] Revert "[DNM]: fix duplicated baseband version string" This reverts commit 8b918fe09852127293283f894a138c7f388a0c94. Signed-off-by: Lucchetto --- telephony/java/android/telephony/TelephonyManager.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index fbd5317e69ed..8ae1ee99b060 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -117,7 +117,6 @@ import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Arrays; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -10103,8 +10102,8 @@ public void setBasebandVersion(String version) { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public void setBasebandVersionForPhone(int phoneId, String version) { if (SubscriptionManager.isValidPhoneId(phoneId)) { - List newList = Arrays.asList(updateTelephonyProperty( - TelephonyProperties.baseband_version(), phoneId, version).get(0)); + List newList = updateTelephonyProperty( + TelephonyProperties.baseband_version(), phoneId, version); TelephonyProperties.baseband_version(newList); } } From b92390c1e135edb29d93564db53dbe84c7faec1e Mon Sep 17 00:00:00 2001 From: Ido Ben-Hur Date: Mon, 12 Oct 2020 19:58:54 +0300 Subject: [PATCH 04/22] SystemUI: Nuke bubble clock First of all it's very ugly, secondly it does not work. AOSP decided to use it for their test module for some reason, so we made it use the analog clock controller instead. Change-Id: Ie2271911f34937f8c24f17929a3858ea8dce6033 --- .../keyguard/clock/AnalogClockController.java | 2 +- .../keyguard/clock/BubbleClockController.java | 190 ------------------ .../android/keyguard/clock/ClockManager.java | 1 - .../clock/BubbleClockControllerTest.java | 75 ------- .../keyguard/clock/ClockManagerTest.java | 64 +++--- 5 files changed, 33 insertions(+), 299 deletions(-) delete mode 100644 packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java delete mode 100644 packages/SystemUI/tests/src/com/android/keyguard/clock/BubbleClockControllerTest.java diff --git a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java index 3cb17126780c..acbf754863fe 100644 --- a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java @@ -70,7 +70,7 @@ public class AnalogClockController implements ClockPlugin { private final ClockPalette mPalette = new ClockPalette(); /** - * Create a BubbleClockController instance. + * Create a AnalogClockController instance. * * @param res Resources contains title and thumbnail. * @param inflater Inflater used to inflate custom clock views. diff --git a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java deleted file mode 100644 index 8a9c410f7802..000000000000 --- a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.keyguard.clock; - -import android.app.WallpaperManager; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Color; -import android.graphics.Paint.Style; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.TextClock; - -import com.android.internal.colorextraction.ColorExtractor; -import com.android.systemui.R; -import com.android.systemui.colorextraction.SysuiColorExtractor; -import com.android.systemui.plugins.ClockPlugin; - -import java.util.TimeZone; - -/** - * Controller for Bubble clock that can appear on lock screen and AOD. - */ -public class BubbleClockController implements ClockPlugin { - - /** - * Resources used to get title and thumbnail. - */ - private final Resources mResources; - - /** - * LayoutInflater used to inflate custom clock views. - */ - private final LayoutInflater mLayoutInflater; - - /** - * Extracts accent color from wallpaper. - */ - private final SysuiColorExtractor mColorExtractor; - - /** - * Renders preview from clock view. - */ - private final ViewPreviewer mRenderer = new ViewPreviewer(); - - /** - * Custom clock shown on AOD screen and behind stack scroller on lock. - */ - private ClockLayout mView; - private ImageClock mAnalogClock; - - /** - * Helper to extract colors from wallpaper palette for clock face. - */ - private final ClockPalette mPalette = new ClockPalette(); - - /** - * Create a BubbleClockController instance. - * - * @param res Resources contains title and thumbnail. - * @param inflater Inflater used to inflate custom clock views. - * @param colorExtractor Extracts accent color from wallpaper. - */ - public BubbleClockController(Resources res, LayoutInflater inflater, - SysuiColorExtractor colorExtractor) { - mResources = res; - mLayoutInflater = inflater; - mColorExtractor = colorExtractor; - } - - private void createViews() { - mView = (ClockLayout) mLayoutInflater.inflate(R.layout.bubble_clock, null); - mAnalogClock = (ImageClock) mView.findViewById(R.id.analog_clock); - } - - @Override - public void onDestroyView() { - mView = null; - mAnalogClock = null; - } - - @Override - public String getName() { - return "bubble"; - } - - @Override - public String getTitle() { - return mResources.getString(R.string.clock_title_bubble); - } - - @Override - public Bitmap getThumbnail() { - return BitmapFactory.decodeResource(mResources, R.drawable.bubble_thumbnail); - } - - @Override - public Bitmap getPreview(int width, int height) { - - // Use the big clock view for the preview - View view = getBigClockView(); - - // Initialize state of plugin before generating preview. - setDarkAmount(1f); - setTextColor(Color.WHITE); - ColorExtractor.GradientColors colors = mColorExtractor.getColors( - WallpaperManager.FLAG_LOCK); - setColorPalette(colors.supportsDarkText(), colors.getColorPalette()); - onTimeTick(); - - return mRenderer.createPreview(view, width, height); - } - - @Override - public View getView() { - return null; - } - - @Override - public View getBigClockView() { - if (mView == null) { - createViews(); - } - return mView; - } - - @Override - public int getPreferredY(int totalHeight) { - return totalHeight / 2; - } - - @Override - public void setTextColor(int color) { - updateColor(); - } - - @Override - public void setColorPalette(boolean supportsDarkText, int[] colorPalette) { - mPalette.setColorPalette(supportsDarkText, colorPalette); - updateColor(); - } - - private void updateColor() { - final int primary = mPalette.getPrimaryColor(); - final int secondary = mPalette.getSecondaryColor(); - mAnalogClock.setClockColors(primary, secondary); - } - - @Override - public void setDarkAmount(float darkAmount) { - mPalette.setDarkAmount(darkAmount); - mView.setDarkAmount(darkAmount); - } - - @Override - public void onTimeTick() { - mAnalogClock.onTimeChanged(); - mView.onTimeChanged(); - } - - @Override - public void onTimeZoneChanged(TimeZone timeZone) { - mAnalogClock.onTimeZoneChanged(timeZone); - } - - @Override - public boolean shouldShowStatusArea() { - return true; - } - - @Override - public boolean shouldShowInBigContainer() { - return true; - } -} diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java index c18708b247e2..e6dbae6b51a8 100644 --- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java +++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java @@ -150,7 +150,6 @@ public ClockManager(Context context, InjectionInflationController injectionInfla LayoutInflater layoutInflater = injectionInflater.injectable(LayoutInflater.from(context)); addBuiltinClock(() -> new DefaultClockController(res, layoutInflater, colorExtractor)); - addBuiltinClock(() -> new BubbleClockController(res, layoutInflater, colorExtractor)); addBuiltinClock(() -> new AnalogClockController(res, layoutInflater, colorExtractor)); addBuiltinClock(() -> new TypeClockController(res, layoutInflater, colorExtractor)); addBuiltinClock(() -> new BinaryClockController(res, layoutInflater, colorExtractor)); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/BubbleClockControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/BubbleClockControllerTest.java deleted file mode 100644 index b56986eb80d0..000000000000 --- a/packages/SystemUI/tests/src/com/android/keyguard/clock/BubbleClockControllerTest.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.keyguard.clock; - -import static com.google.common.truth.Truth.assertThat; - -import android.content.res.Resources; -import android.graphics.Color; -import android.test.suitebuilder.annotation.SmallTest; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper.RunWithLooper; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.colorextraction.SysuiColorExtractor; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -@RunWithLooper -public final class BubbleClockControllerTest extends SysuiTestCase { - - private BubbleClockController mClockController; - @Mock SysuiColorExtractor mMockColorExtractor; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - Resources res = getContext().getResources(); - LayoutInflater layoutInflater = LayoutInflater.from(getContext()); - mClockController = new BubbleClockController(res, layoutInflater, mMockColorExtractor); - } - - @Test - public void setDarkAmount_AOD() { - ViewGroup smallClockFrame = (ViewGroup) mClockController.getView(); - View smallClock = smallClockFrame.getChildAt(0); - // WHEN dark amount is set to AOD - mClockController.setDarkAmount(1f); - // THEN small clock should not be shown. - assertThat(smallClock.getVisibility()).isEqualTo(View.VISIBLE); - } - - @Test - public void setColorPalette_setDigitalClock() { - ViewGroup smallClock = (ViewGroup) mClockController.getView(); - // WHEN text color is set - mClockController.setColorPalette(true, new int[]{Color.RED}); - // THEN child of small clock should have text color set. - TextView digitalClock = (TextView) smallClock.getChildAt(0); - assertThat(digitalClock.getCurrentTextColor()).isEqualTo(Color.RED); - } -} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java index 353fe625b8ea..a2c0b484549f 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java @@ -59,8 +59,8 @@ @RunWithLooper(setAsMainLooper = true) public final class ClockManagerTest extends SysuiTestCase { - private static final String BUBBLE_CLOCK = BubbleClockController.class.getName(); - private static final Class BUBBLE_CLOCK_CLASS = BubbleClockController.class; + private static final String ANALOG_CLOCK = AnalogClockController.class.getName(); + private static final Class ANALOG_CLOCK_CLASS = AnalogClockController.class; private static final int MAIN_USER_ID = 0; private static final int SECONDARY_USER_ID = 11; private static final Uri SETTINGS_URI = null; @@ -95,7 +95,7 @@ public void setUp() { mMockPluginManager, mMockColorExtractor, mMockContentResolver, mMockCurrentUserObserable, mMockSettingsWrapper, mFakeDockManager); - mClockManager.addBuiltinClock(() -> new BubbleClockController( + mClockManager.addBuiltinClock(() -> new AnalogClockController( getContext().getResources(), inflater, mMockColorExtractor)); mClockManager.addOnClockChangedListener(mMockListener1); mClockManager.addOnClockChangedListener(mMockListener2); @@ -135,39 +135,39 @@ public void getCurrentClock_default() { @Test public void getCurrentClock_customClock() { - // GIVEN that settings is set to the bubble clock face - when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(BUBBLE_CLOCK); + // GIVEN that settings is set to the analog clock face + when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(ANALOG_CLOCK); // WHEN settings change event is fired mContentObserver.onChange(false, Arrays.asList(SETTINGS_URI), 0, MAIN_USER_ID); - // THEN the plugin is the bubble clock face. - assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS); + // THEN the plugin is the analog clock face. + assertThat(mClockManager.getCurrentClock()).isInstanceOf(ANALOG_CLOCK_CLASS); } @Test public void onClockChanged_customClock() { - // GIVEN that settings is set to the bubble clock face - when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(BUBBLE_CLOCK); + // GIVEN that settings is set to the analog clock face + when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(ANALOG_CLOCK); // WHEN settings change event is fired mContentObserver.onChange(false, Arrays.asList(SETTINGS_URI), 0, MAIN_USER_ID); - // THEN the plugin is the bubble clock face. + // THEN the plugin is the analog clock face. ArgumentCaptor captor = ArgumentCaptor.forClass(ClockPlugin.class); verify(mMockListener1).onClockChanged(captor.capture()); - assertThat(captor.getValue()).isInstanceOf(BUBBLE_CLOCK_CLASS); + assertThat(captor.getValue()).isInstanceOf(ANALOG_CLOCK_CLASS); } @Test public void onClockChanged_uniqueInstances() { - // GIVEN that settings is set to the bubble clock face - when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(BUBBLE_CLOCK); + // GIVEN that settings is set to the analog clock face + when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(ANALOG_CLOCK); // WHEN settings change event is fired mContentObserver.onChange(false, Arrays.asList(SETTINGS_URI), 0, MAIN_USER_ID); - // THEN the listeners receive separate instances of the Bubble clock plugin. + // THEN the listeners receive separate instances of the analog clock plugin. ArgumentCaptor captor1 = ArgumentCaptor.forClass(ClockPlugin.class); ArgumentCaptor captor2 = ArgumentCaptor.forClass(ClockPlugin.class); verify(mMockListener1).onClockChanged(captor1.capture()); verify(mMockListener2).onClockChanged(captor2.capture()); - assertThat(captor1.getValue()).isInstanceOf(BUBBLE_CLOCK_CLASS); - assertThat(captor2.getValue()).isInstanceOf(BUBBLE_CLOCK_CLASS); + assertThat(captor1.getValue()).isInstanceOf(ANALOG_CLOCK_CLASS); + assertThat(captor2.getValue()).isInstanceOf(ANALOG_CLOCK_CLASS); assertThat(captor1.getValue()).isNotSameAs(captor2.getValue()); } @@ -192,12 +192,12 @@ public void getCurrentClock_dockedDefault() { @Test public void getCurrentClock_dockedCustomClock() { - // GIVEN settings is set to the bubble clock face - when(mMockSettingsWrapper.getDockedClockFace(anyInt())).thenReturn(BUBBLE_CLOCK); + // GIVEN settings is set to the analog clock face + when(mMockSettingsWrapper.getDockedClockFace(anyInt())).thenReturn(ANALOG_CLOCK); // WHEN dock event fires mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED); - // THEN the plugin is the bubble clock face. - assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS); + // THEN the plugin is the analog clock face. + assertThat(mClockManager.getCurrentClock()).isInstanceOf(ANALOG_CLOCK_CLASS); } @Test @@ -213,13 +213,13 @@ public void getCurrentClock_badDockedSettingsValue() { @Test public void getCurrentClock_badDockedSettingsFallback() { // GIVEN settings contains a value that doesn't correspond to an available clock face, but - // locked screen settings is set to bubble clock. + // locked screen settings is set to analog clock. when(mMockSettingsWrapper.getDockedClockFace(anyInt())).thenReturn("bad value"); - when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(BUBBLE_CLOCK); + when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(ANALOG_CLOCK); // WHEN dock event is fired mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED); - // THEN the plugin is the bubble clock face. - assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS); + // THEN the plugin is the analog clock face. + assertThat(mClockManager.getCurrentClock()).isInstanceOf(ANALOG_CLOCK_CLASS); } @Test @@ -232,24 +232,24 @@ public void onUserChanged_defaultClock() { @Test public void onUserChanged_customClock() { - // GIVEN that a second user has selected the bubble clock face + // GIVEN that a second user has selected the analog clock face when(mMockSettingsWrapper.getLockScreenCustomClockFace(SECONDARY_USER_ID)).thenReturn( - BUBBLE_CLOCK); + ANALOG_CLOCK); // WHEN the user changes mCurrentUser.setValue(SECONDARY_USER_ID); - // THEN the plugin is the bubble clock face. - assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS); + // THEN the plugin is the analog clock face. + assertThat(mClockManager.getCurrentClock()).isInstanceOf(ANALOG_CLOCK_CLASS); } @Test public void onUserChanged_docked() { // GIVEN device is docked mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED); - // AND the second user as selected the bubble clock for the dock - when(mMockSettingsWrapper.getDockedClockFace(SECONDARY_USER_ID)).thenReturn(BUBBLE_CLOCK); + // AND the second user as selected the analog clock for the dock + when(mMockSettingsWrapper.getDockedClockFace(SECONDARY_USER_ID)).thenReturn(ANALOG_CLOCK); // WHEN the user changes mCurrentUser.setValue(SECONDARY_USER_ID); - // THEN the plugin is the bubble clock face. - assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS); + // THEN the plugin is the analog clock face. + assertThat(mClockManager.getCurrentClock()).isInstanceOf(ANALOG_CLOCK_CLASS); } } From 258004e1e498658f31ca80f45af46b4ff0675239 Mon Sep 17 00:00:00 2001 From: maxwen Date: Sun, 18 Oct 2020 17:33:26 +0200 Subject: [PATCH 05/22] SystemUI: use DOUBLE_TAP_TO_WAKE setting also for wake from aod Change-Id: I844e36469b0e3b0168eecb62f6e8b0e9b2bff40e --- .../NotificationShadeWindowViewController.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java index bd13a5773833..a58387b7da60 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java @@ -107,6 +107,9 @@ public class NotificationShadeWindowViewController { private RectF mTempRect = new RectF(); private boolean mIsTrackingBarGesture = false; + // custom additions start + private boolean mDoubleTapEnabledNative; + @Inject public NotificationShadeWindowViewController( InjectionInflationController injectionInflationController, @@ -170,11 +173,17 @@ public void setupExpandedStatusBar() { break; case Settings.Secure.DOZE_TAP_SCREEN_GESTURE: mSingleTapEnabled = configuration.tapGestureEnabled(UserHandle.USER_CURRENT); + break; + case Settings.Secure.DOUBLE_TAP_TO_WAKE: + mDoubleTapEnabledNative = Settings.Secure.getIntForUser(mView.getContext().getContentResolver(), + Settings.Secure.DOUBLE_TAP_TO_WAKE, 0, UserHandle.USER_CURRENT) == 1; + break; } }; mTunerService.addTunable(tunable, Settings.Secure.DOZE_DOUBLE_TAP_GESTURE, - Settings.Secure.DOZE_TAP_SCREEN_GESTURE); + Settings.Secure.DOZE_TAP_SCREEN_GESTURE, + Settings.Secure.DOUBLE_TAP_TO_WAKE); GestureDetector.SimpleOnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener() { @@ -190,7 +199,7 @@ public boolean onSingleTapConfirmed(MotionEvent e) { @Override public boolean onDoubleTap(MotionEvent e) { - if (mDoubleTapEnabled || mSingleTapEnabled) { + if (mDoubleTapEnabled || mSingleTapEnabled || mDoubleTapEnabledNative) { mService.wakeUpIfDozing( SystemClock.uptimeMillis(), mView, "DOUBLE_TAP"); return true; From c066e94a3f9d154885d8bcdceef275d07eae6633 Mon Sep 17 00:00:00 2001 From: Henrique Silva Date: Tue, 7 May 2019 13:29:38 -0300 Subject: [PATCH 06/22] base: Improvements for swipe to screenshot * Cancel touch events when tap with three fingers * Dyrex2004: Fix typos (Screnshot -> Screenshot) --- core/java/android/app/IActivityManager.aidl | 5 +++ core/java/android/view/ViewRootImpl.java | 14 +++++++ .../server/am/ActivityManagerService.java | 41 +++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 945957738f8e..8dde6bb9f151 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -683,4 +683,9 @@ interface IActivityManager { * Kills uid with the reason of permission change. */ void killUidForPermissionChange(int appId, int userId, String reason); + + /** + * Should disable touch if three fingers to screen shot is active? + */ + boolean isSwipeToScreenshotGestureActive(); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index a7aa33f61c85..bb7dbc93ae77 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -5996,6 +5996,11 @@ private int processKeyEvent(QueuedInputEvent q) { private int processPointerEvent(QueuedInputEvent q) { final MotionEvent event = (MotionEvent)q.mEvent; + if (event.getPointerCount() == 3 && isSwipeToScreenshotGestureActive()) { + event.setAction(MotionEvent.ACTION_CANCEL); + Log.d("teste", "canceling motionEvent because of threeGesture detecting"); + } + mAttachInfo.mUnbufferedDispatchRequested = false; mAttachInfo.mHandlingPointerEvent = true; boolean handled = mView.dispatchPointerEvent(event); @@ -9851,4 +9856,13 @@ boolean useBLAST() { boolean isDrawingToBLASTTransaction() { return mNextReportConsumeBLAST; } + + private boolean isSwipeToScreenshotGestureActive() { + try { + return ActivityManager.getService().isSwipeToScreenshotGestureActive(); + } catch (RemoteException e) { + Log.e("teste", "isSwipeToScreenshotGestureActive exception", e); + return false; + } + } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 0b2f8eae0f75..2357a4e30a24 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1672,6 +1672,10 @@ public void binderDied() { private ParcelFileDescriptor[] mLifeMonitorFds; static final HostingRecord sNullHostingRecord = new HostingRecord(null); + + final SwipeToScreenshotObserver mSwipeToScreenshotObserver; + private boolean mIsSwipeToScreenshotEnabled; + /** * Used to notify activity lifecycle events. */ @@ -2566,6 +2570,7 @@ public ActivityManagerService(Injector injector, ServiceThread handlerThread) { mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class); mInternal = new LocalService(); mPendingStartActivityUids = new PendingStartActivityUids(mContext); + mSwipeToScreenshotObserver = null; } // Note: This method is invoked on the main thread but may need to attach various @@ -2724,6 +2729,7 @@ public void run() { mInternal = new LocalService(); mPendingStartActivityUids = new PendingStartActivityUids(mContext); + mSwipeToScreenshotObserver = new SwipeToScreenshotObserver(mHandler, mContext); } public void setSystemServiceManager(SystemServiceManager mgr) { @@ -9525,6 +9531,7 @@ private void retrieveSettings() { mWaitForNetworkTimeoutMs = waitForNetworkTimeoutMs; mPssDeferralTime = pssDeferralMs; } + mSwipeToScreenshotObserver.registerObserver(); } /** @@ -20375,4 +20382,38 @@ public boolean isAppFreezerSupported() { Binder.restoreCallingIdentity(token); } } + + private class SwipeToScreenshotObserver extends ContentObserver { + + private final Context mContext; + + public SwipeToScreenshotObserver(Handler handler, Context context) { + super(handler); + mContext = context; + } + + public void registerObserver() { + mContext.getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.System.THREE_FINGER_GESTURE), + false, this, UserHandle.USER_ALL); + update(); + } + + private void update() { + mIsSwipeToScreenshotEnabled = Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.THREE_FINGER_GESTURE, 1, UserHandle.USER_CURRENT) == 1; + } + + public void onChange(boolean selfChange) { + update(); + } + } + + @Override + public boolean isSwipeToScreenshotGestureActive() { + synchronized (this) { + return mIsSwipeToScreenshotEnabled; + } + } + } From 2707d34b79e577e1bdc0e70e4fc1f71f73a5c9f0 Mon Sep 17 00:00:00 2001 From: Henrique Silva Date: Fri, 3 May 2019 16:49:15 -0300 Subject: [PATCH 07/22] Introduce LiveDisplay from Lineage All credits to LineageOS and related authors on: https://github.com/LineageOS/android_lineage-sdk Included fwb commit: commit 5ef7a9bb0996cc0c141d054514744ceaa93ba036 Author: Dan Pasanen Date: Tue Feb 20 07:51:43 2018 -0600 NightDisplayController: report unavailable if livedisplay feature is present Change-Id: Id677e76d452ced2e62373337d63593c7b85f2fba Syberia: forward port to 10, 11 squashed with: lineage-sdk: ServiceType.GPS -> ServiceType.LOCATION lineage-sdk: Update DisplayTransformManager class path Signed-off-by: Henrique Silva Change-Id: I35bf801aca2e31f30e9d1a0488648f93eab84492 Signed-off-by: DennySPb --- Android.bp | 1 + core/java/android/provider/Settings.java | 74 ++ .../custom/app/LineageContextConstants.java | 53 ++ .../custom/hardware/AdaptiveBacklight.java | 63 ++ .../custom/hardware/AutoContrast.java | 75 ++ .../custom/hardware/ColorBalance.java | 75 ++ .../custom/hardware/ColorEnhancement.java | 66 ++ .../hardware/DisplayColorCalibration.java | 55 ++ .../internal/custom/hardware/DisplayMode.aidl | 19 + .../internal/custom/hardware/DisplayMode.java | 105 +++ .../custom/hardware/DisplayModeControl.java | 78 ++ .../internal/custom/hardware/HIDLHelper.java | 67 ++ .../internal/custom/hardware/HSIC.aidl | 19 + .../internal/custom/hardware/HSIC.java | 128 +++ .../hardware/ILineageHardwareService.aidl | 51 ++ .../custom/hardware/ILiveDisplayService.aidl | 56 ++ .../hardware/LineageHardwareManager.java | 758 ++++++++++++++++++ .../custom/hardware/LiveDisplayConfig.aidl | 19 + .../custom/hardware/LiveDisplayConfig.java | 386 +++++++++ .../custom/hardware/LiveDisplayManager.java | 505 ++++++++++++ .../custom/hardware/PictureAdjustment.java | 116 +++ .../custom/hardware/ReadingEnhancement.java | 60 ++ .../custom/hardware/SunlightEnhancement.java | 104 +++ .../internal/util/custom/ColorUtils.java | 532 ++++++++++++ .../internal/util/custom/Concierge.java | 151 ++++ .../internal/util/custom/FileUtils.java | 162 ++++ .../internal/util/custom/MathUtils.java | 67 ++ .../custom/palette/ColorCutQuantizer.java | 517 ++++++++++++ .../util/custom/palette/ColorUtils.java | 299 +++++++ .../util/custom/palette/DefaultGenerator.java | 244 ++++++ .../internal/util/custom/palette/Palette.java | 740 +++++++++++++++++ core/res/AndroidManifest.xml | 11 + core/res/res-revengeos/values/arrays.xml | 73 ++ core/res/res-revengeos/values/config.xml | 31 + core/res/res-revengeos/values/strings.xml | 24 + core/res/res-revengeos/values/symbols.xml | 32 + core/res/res/drawable/ic_livedisplay_auto.xml | 29 + core/res/res/drawable/ic_livedisplay_day.xml | 30 + .../res/res/drawable/ic_livedisplay_night.xml | 29 + .../res/res/drawable/ic_livedisplay_notif.xml | 29 + core/res/res/drawable/ic_livedisplay_off.xml | 30 + .../res/drawable/ic_livedisplay_outdoor.xml | 27 + .../server/custom/LineageHardwareService.java | 568 +++++++++++++ .../custom/common/UserContentObserver.java | 95 +++ .../custom/display/AmbientLuxObserver.java | 259 ++++++ .../display/ColorTemperatureController.java | 372 +++++++++ .../display/DisplayHardwareController.java | 512 ++++++++++++ .../custom/display/LiveDisplayFeature.java | 181 +++++ .../custom/display/LiveDisplayService.java | 557 +++++++++++++ .../custom/display/OutdoorModeController.java | 258 ++++++ .../display/PictureAdjustmentController.java | 242 ++++++ .../custom/display/TwilightCalculator.java | 123 +++ .../custom/display/TwilightTracker.java | 561 +++++++++++++ .../java/com/android/server/SystemServer.java | 12 + 54 files changed, 9730 insertions(+) create mode 100644 core/java/com/android/internal/custom/app/LineageContextConstants.java create mode 100644 core/java/com/android/internal/custom/hardware/AdaptiveBacklight.java create mode 100644 core/java/com/android/internal/custom/hardware/AutoContrast.java create mode 100644 core/java/com/android/internal/custom/hardware/ColorBalance.java create mode 100644 core/java/com/android/internal/custom/hardware/ColorEnhancement.java create mode 100644 core/java/com/android/internal/custom/hardware/DisplayColorCalibration.java create mode 100644 core/java/com/android/internal/custom/hardware/DisplayMode.aidl create mode 100644 core/java/com/android/internal/custom/hardware/DisplayMode.java create mode 100644 core/java/com/android/internal/custom/hardware/DisplayModeControl.java create mode 100644 core/java/com/android/internal/custom/hardware/HIDLHelper.java create mode 100644 core/java/com/android/internal/custom/hardware/HSIC.aidl create mode 100644 core/java/com/android/internal/custom/hardware/HSIC.java create mode 100644 core/java/com/android/internal/custom/hardware/ILineageHardwareService.aidl create mode 100644 core/java/com/android/internal/custom/hardware/ILiveDisplayService.aidl create mode 100644 core/java/com/android/internal/custom/hardware/LineageHardwareManager.java create mode 100644 core/java/com/android/internal/custom/hardware/LiveDisplayConfig.aidl create mode 100644 core/java/com/android/internal/custom/hardware/LiveDisplayConfig.java create mode 100644 core/java/com/android/internal/custom/hardware/LiveDisplayManager.java create mode 100644 core/java/com/android/internal/custom/hardware/PictureAdjustment.java create mode 100644 core/java/com/android/internal/custom/hardware/ReadingEnhancement.java create mode 100644 core/java/com/android/internal/custom/hardware/SunlightEnhancement.java create mode 100644 core/java/com/android/internal/util/custom/ColorUtils.java create mode 100644 core/java/com/android/internal/util/custom/Concierge.java create mode 100644 core/java/com/android/internal/util/custom/FileUtils.java create mode 100644 core/java/com/android/internal/util/custom/MathUtils.java create mode 100644 core/java/com/android/internal/util/custom/palette/ColorCutQuantizer.java create mode 100644 core/java/com/android/internal/util/custom/palette/ColorUtils.java create mode 100644 core/java/com/android/internal/util/custom/palette/DefaultGenerator.java create mode 100644 core/java/com/android/internal/util/custom/palette/Palette.java create mode 100644 core/res/res-revengeos/values/arrays.xml create mode 100644 core/res/res/drawable/ic_livedisplay_auto.xml create mode 100644 core/res/res/drawable/ic_livedisplay_day.xml create mode 100644 core/res/res/drawable/ic_livedisplay_night.xml create mode 100644 core/res/res/drawable/ic_livedisplay_notif.xml create mode 100644 core/res/res/drawable/ic_livedisplay_off.xml create mode 100644 core/res/res/drawable/ic_livedisplay_outdoor.xml create mode 100644 services/core/java/com/android/server/custom/LineageHardwareService.java create mode 100644 services/core/java/com/android/server/custom/common/UserContentObserver.java create mode 100644 services/core/java/com/android/server/custom/display/AmbientLuxObserver.java create mode 100644 services/core/java/com/android/server/custom/display/ColorTemperatureController.java create mode 100644 services/core/java/com/android/server/custom/display/DisplayHardwareController.java create mode 100644 services/core/java/com/android/server/custom/display/LiveDisplayFeature.java create mode 100644 services/core/java/com/android/server/custom/display/LiveDisplayService.java create mode 100644 services/core/java/com/android/server/custom/display/OutdoorModeController.java create mode 100644 services/core/java/com/android/server/custom/display/PictureAdjustmentController.java create mode 100644 services/core/java/com/android/server/custom/display/TwilightCalculator.java create mode 100644 services/core/java/com/android/server/custom/display/TwilightTracker.java diff --git a/Android.bp b/Android.bp index bf6c99d0cf29..ee7939480372 100644 --- a/Android.bp +++ b/Android.bp @@ -387,6 +387,7 @@ java_library { "com.android.sysprop.apex", "com.android.sysprop.init", "PlatformProperties", + "vendor.lineage.livedisplay-V2.0-java", ], sdk_version: "core_platform", installable: false, diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index fbdf7fe2eec5..e7060a08f994 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4904,6 +4904,69 @@ public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean */ public static final String LOCKSCREEN_BATTERY_INFO = "lockscreen_battery_info"; + /** + * Color temperature of the display during the day + */ + public static final String DISPLAY_TEMPERATURE_DAY = "display_temperature_day"; + + /** + * Color temperature of the display at night + */ + public static final String DISPLAY_TEMPERATURE_NIGHT = "display_temperature_night"; + + /** + * Display color temperature adjustment mode, one of DAY (default), NIGHT, or AUTO. + */ + public static final String DISPLAY_TEMPERATURE_MODE = "display_temperature_mode"; + + /** + * Automatic outdoor mode + * 0 = 0ff, 1 = on + */ + public static final String DISPLAY_AUTO_OUTDOOR_MODE = "display_auto_outdoor_mode"; + + /** + * Reader mode + * 0 = 0ff, 1 = on + */ + public static final String DISPLAY_READING_MODE = "display_reading_mode"; + + /** + * Use display power saving features such as CABC or CABL + * 0 = 0ff, 1 = on + */ + public static final String DISPLAY_CABC = "display_low_power"; + + /** + * Use color enhancement feature of display + * 0 = 0ff, 1 = on + */ + public static final String DISPLAY_COLOR_ENHANCE = "display_color_enhance"; + + /** + * Use auto contrast optimization feature of display + * 0 = 0ff, 1 = on + */ + public static final String DISPLAY_AUTO_CONTRAST = "display_auto_contrast"; + + /** + * Manual display color adjustments (RGB values as floats, separated by spaces) + */ + public static final String DISPLAY_COLOR_ADJUSTMENT = "display_color_adjustment"; + + /** + * The current custom picture adjustment values as a delimited string + */ + public static final String DISPLAY_PICTURE_ADJUSTMENT = + "display_picture_adjustment"; + + /** + * Did we tell about how they can stop breaking their eyes? + * @hide + */ + public static final String LIVE_DISPLAY_HINTED = "live_display_hinted"; + + /** * Keys we no longer back up under the current schema, but want to continue to * process when restoring historical backup datasets. @@ -5037,6 +5100,17 @@ public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean PRIVATE_SETTINGS.add(KEY_APP_SWITCH_LONG_PRESS_ACTION); PRIVATE_SETTINGS.add(LOCKSCREEN_MEDIA_ART); PRIVATE_SETTINGS.add(POCKET_JUDGE); + PRIVATE_SETTINGS.add(DISPLAY_TEMPERATURE_DAY); + PRIVATE_SETTINGS.add(DISPLAY_TEMPERATURE_NIGHT); + PRIVATE_SETTINGS.add(DISPLAY_TEMPERATURE_MODE); + PRIVATE_SETTINGS.add(DISPLAY_AUTO_OUTDOOR_MODE); + PRIVATE_SETTINGS.add(DISPLAY_READING_MODE); + PRIVATE_SETTINGS.add(DISPLAY_CABC); + PRIVATE_SETTINGS.add(DISPLAY_COLOR_ENHANCE); + PRIVATE_SETTINGS.add(DISPLAY_AUTO_CONTRAST); + PRIVATE_SETTINGS.add(DISPLAY_COLOR_ADJUSTMENT); + PRIVATE_SETTINGS.add(DISPLAY_PICTURE_ADJUSTMENT); + PRIVATE_SETTINGS.add(LIVE_DISPLAY_HINTED); } /** diff --git a/core/java/com/android/internal/custom/app/LineageContextConstants.java b/core/java/com/android/internal/custom/app/LineageContextConstants.java new file mode 100644 index 000000000000..589270b54517 --- /dev/null +++ b/core/java/com/android/internal/custom/app/LineageContextConstants.java @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2015, 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 com.android.internal.custom.app; + +import android.annotation.SdkConstant; + +/** + * @hide + * TODO: We need to somehow make these managers accessible via getSystemService + */ +public final class LineageContextConstants { + + /** + * @hide + */ + private LineageContextConstants() { + // Empty constructor + } + + /** + * Use with {@link android.content.Context#getSystemService} to retrieve a + * {@link com.android.internal.custom.LineageHardwareManager} to manage the extended + * hardware features of the device. + * + * @see android.content.Context#getSystemService + * @see com.android.internal.custom.LineageHardwareManager + * + * @hide + */ + public static final String LINEAGE_HARDWARE_SERVICE = "lineagehardware"; + + /** + * Manages display color adjustments + * + * @hide + */ + public static final String LINEAGE_LIVEDISPLAY_SERVICE = "lineagelivedisplay"; + +} diff --git a/core/java/com/android/internal/custom/hardware/AdaptiveBacklight.java b/core/java/com/android/internal/custom/hardware/AdaptiveBacklight.java new file mode 100644 index 000000000000..11a1c72d52dc --- /dev/null +++ b/core/java/com/android/internal/custom/hardware/AdaptiveBacklight.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * Copyright (C) 2018 The LineageOS Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.custom.hardware; + +import android.util.Log; + +import com.android.internal.util.custom.FileUtils; + +/** + * Adaptive backlight support (this refers to technologies like NVIDIA SmartDimmer, + * QCOM CABL or Samsung CABC). + */ +public class AdaptiveBacklight { + + private static final String TAG = "AdaptiveBacklight"; + + private static final String FILE_CABC = "/sys/class/graphics/fb0/cabc"; + + /** + * Whether device supports an adaptive backlight technology. + * + * @return boolean Supported devices must return always true + */ + public static boolean isSupported() { + return FileUtils.isFileReadable(FILE_CABC) && FileUtils.isFileWritable(FILE_CABC); + } + + /** + * This method return the current activation status of the adaptive backlight technology. + * + * @return boolean Must be false when adaptive backlight is not supported or not activated, or + * the operation failed while reading the status; true in any other case. + */ + public static boolean isEnabled() { + return Integer.parseInt(FileUtils.readOneLine(FILE_CABC)) > 0; + } + + /** + * This method allows to setup adaptive backlight technology status. + * + * @param status The new adaptive backlight status + * @return boolean Must be false if adaptive backlight is not supported or the operation + * failed; true in any other case. + */ + public static boolean setEnabled(boolean status) { + return FileUtils.writeLine(FILE_CABC, status ? "1" : "0"); + } +} diff --git a/core/java/com/android/internal/custom/hardware/AutoContrast.java b/core/java/com/android/internal/custom/hardware/AutoContrast.java new file mode 100644 index 000000000000..2de28e541b4b --- /dev/null +++ b/core/java/com/android/internal/custom/hardware/AutoContrast.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2014 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 com.android.internal.custom.hardware; + +import com.android.internal.util.custom.FileUtils; + +import android.util.Log; + +/** + * Auto Contrast Optimization + */ +public class AutoContrast { + + private static final String TAG = "AutoContrast"; + + private static final String FILE_ACO = "/sys/class/graphics/fb0/aco"; + + /** + * Whether device supports ACO + * + * @return boolean Supported devices must return always true + */ + public static boolean isSupported() { + return FileUtils.isFileReadable(FILE_ACO) && FileUtils.isFileWritable(FILE_ACO); + } + + /** + * This method return the current activation status of ACO + * + * @return boolean Must be false when ACO is not supported or not activated, or + * the operation failed while reading the status; true in any other case. + */ + public static boolean isEnabled() { + try { + return Integer.parseInt(FileUtils.readOneLine(FILE_ACO)) > 0; + } catch (Exception e) { + Log.e(TAG, e.getMessage(), e); + } + return false; + } + + /** + * This method allows to setup ACO + * + * @param status The new ACO status + * @return boolean Must be false if ACO is not supported or the operation + * failed; true in any other case. + */ + public static boolean setEnabled(boolean status) { + return FileUtils.writeLine(FILE_ACO, status ? "1" : "0"); + } + + /** + * Whether adaptive backlight (CABL / CABC) is required to be enabled + * + * @return boolean False if adaptive backlight is not a dependency + */ + public static boolean isAdaptiveBacklightRequired() { + return false; + } +} diff --git a/core/java/com/android/internal/custom/hardware/ColorBalance.java b/core/java/com/android/internal/custom/hardware/ColorBalance.java new file mode 100644 index 000000000000..db4496614b6b --- /dev/null +++ b/core/java/com/android/internal/custom/hardware/ColorBalance.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 The CyanogenMod Project + * Copyright (C) 2018 The LineageOS Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.custom.hardware; + +/** + * Color balance support + * + * Color balance controls allow direct adjustment of display color temperature + * using a range of values. A zero implies no adjustment, negative values + * move towards warmer temperatures, and positive values move towards + * cool temperatures. + */ +public class ColorBalance { + + /** + * Whether device supports color balance control + * + * @return boolean Supported devices must return always true + */ + public static boolean isSupported() { + return false; + } + + /** + * This method returns the current color balance value + * + * @return int Zero when no adjustment is made, negative values move + * towards warmer temperatures, positive values move towards cooler temperatures. + */ + public static int getValue() { + return 0; + } + + /** + * This method allows to set the display color balance + * + * @param value + * @return boolean Must be false if feature is not supported or the operation + * failed; true in any other case. + */ + public static boolean setValue(int value) { + return false; + } + + /** + * Get the minimum allowed color adjustment value + * @return int + */ + public static int getMinValue() { + return 0; + } + + /** + * Get the maximum allowed color adjustment value + * @return int + */ + public static int getMaxValue() { + return 0; + } +} diff --git a/core/java/com/android/internal/custom/hardware/ColorEnhancement.java b/core/java/com/android/internal/custom/hardware/ColorEnhancement.java new file mode 100644 index 000000000000..c135bbe45d7a --- /dev/null +++ b/core/java/com/android/internal/custom/hardware/ColorEnhancement.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2013 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 com.android.internal.custom.hardware; + +import com.android.internal.util.custom.FileUtils; + +import android.util.Log; + +/** + * Color enhancement support + */ +public class ColorEnhancement { + + private static final String TAG = "ColorEnhancement"; + + private static final String FILE_CE = "/sys/class/graphics/fb0/color_enhance"; + + /** + * Whether device supports an color enhancement technology. + * + * @return boolean Supported devices must return always true + */ + public static boolean isSupported() { + return FileUtils.isFileReadable(FILE_CE) && FileUtils.isFileWritable(FILE_CE); + } + + /** + * This method return the current activation status of the color enhancement technology. + * + * @return boolean Must be false when color enhancement is not supported or not activated, or + * the operation failed while reading the status; true in any other case. + */ + public static boolean isEnabled() { + try { + return Integer.parseInt(FileUtils.readOneLine(FILE_CE)) > 0; + } catch (Exception e) { + Log.e(TAG, e.getMessage(), e); + } + return false; + } + + /** + * This method allows to setup color enhancement technology status. + * + * @param status The new color enhancement status + * @return boolean Must be false if adaptive backlight is not supported or the operation + * failed; true in any other case. + */ + public static boolean setEnabled(boolean status) { + return FileUtils.writeLine(FILE_CE, status ? "1" : "0"); + } +} diff --git a/core/java/com/android/internal/custom/hardware/DisplayColorCalibration.java b/core/java/com/android/internal/custom/hardware/DisplayColorCalibration.java new file mode 100644 index 000000000000..f34fd0495ba6 --- /dev/null +++ b/core/java/com/android/internal/custom/hardware/DisplayColorCalibration.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * Copyright (C) 2018 The LineageOS Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.custom.hardware; + +import com.android.internal.util.custom.FileUtils; + +public class DisplayColorCalibration { + + private static final String TAG = "DisplayColorCalibration"; + + private static final String COLOR_FILE = "/sys/class/graphics/fb0/rgb"; + + private static final int MIN = 255; + private static final int MAX = 32768; + + public static boolean isSupported() { + return FileUtils.isFileReadable(COLOR_FILE) && FileUtils.isFileWritable(COLOR_FILE); + } + + public static int getMaxValue() { + return MAX; + } + + public static int getMinValue() { + return MIN; + } + + public static int getDefValue() { + return getMaxValue(); + } + + public static String getCurColors() { + return FileUtils.readOneLine(COLOR_FILE); + } + + public static boolean setColors(String colors) { + return FileUtils.writeLine(COLOR_FILE, colors); + } + +} diff --git a/core/java/com/android/internal/custom/hardware/DisplayMode.aidl b/core/java/com/android/internal/custom/hardware/DisplayMode.aidl new file mode 100644 index 000000000000..faf938d922cf --- /dev/null +++ b/core/java/com/android/internal/custom/hardware/DisplayMode.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2015 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 com.android.internal.custom.hardware; + +parcelable DisplayMode; diff --git a/core/java/com/android/internal/custom/hardware/DisplayMode.java b/core/java/com/android/internal/custom/hardware/DisplayMode.java new file mode 100644 index 000000000000..4a36aef7313b --- /dev/null +++ b/core/java/com/android/internal/custom/hardware/DisplayMode.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2015 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 com.android.internal.custom.hardware; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.custom.Concierge; +import com.android.internal.util.custom.Concierge.ParcelInfo; + +/** + * Display Modes API + * + * A device may implement a list of preset display modes for different + * viewing intents, such as movies, photos, or extra vibrance. These + * modes may have multiple components such as gamma correction, white + * point adjustment, etc, but are activated by a single control point. + * + * This API provides support for enumerating and selecting the + * modes supported by the hardware. + * + * A DisplayMode is referenced by it's identifier and carries an + * associated name (up to the user to translate this value). + */ +public class DisplayMode implements Parcelable { + public final int id; + public final String name; + + public DisplayMode(int id, String name) { + this.id = id; + this.name = name; + } + + private DisplayMode(Parcel parcel) { + // Read parcelable version via the Concierge + ParcelInfo parcelInfo = Concierge.receiveParcel(parcel); + int parcelableVersion = parcelInfo.getParcelVersion(); + + // temp vars + int tmpId = -1; + String tmpName = null; + + tmpId = parcel.readInt(); + if (parcel.readInt() != 0) { + tmpName = parcel.readString(); + } + + // set temps + this.id = tmpId; + this.name = tmpName; + + // Complete parcel info for the concierge + parcelInfo.complete(); + } + + @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); + + out.writeInt(id); + if (name != null) { + out.writeInt(1); + out.writeString(name); + } else { + out.writeInt(0); + } + + // Complete the parcel info for the concierge + parcelInfo.complete(); + } + + /** @hide */ + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public DisplayMode createFromParcel(Parcel in) { + return new DisplayMode(in); + } + + @Override + public DisplayMode[] newArray(int size) { + return new DisplayMode[size]; + } + }; + +} diff --git a/core/java/com/android/internal/custom/hardware/DisplayModeControl.java b/core/java/com/android/internal/custom/hardware/DisplayModeControl.java new file mode 100644 index 000000000000..206075649fd2 --- /dev/null +++ b/core/java/com/android/internal/custom/hardware/DisplayModeControl.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * Copyright (C) 2018 The LineageOS Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.custom.hardware; + +/** + * Display Modes API + * + * A device may implement a list of preset display modes for different + * viewing intents, such as movies, photos, or extra vibrance. These + * modes may have multiple components such as gamma correction, white + * point adjustment, etc, but are activated by a single control point. + * + * This API provides support for enumerating and selecting the + * modes supported by the hardware. + */ + +public class DisplayModeControl { + + /** + * All HAF classes should export this boolean. + * Real implementations must, of course, return true + */ + public static boolean isSupported() { + return false; + } + + /** + * Get the list of available modes. A mode has an integer + * identifier and a string name. + * + * It is the responsibility of the upper layers to + * map the name to a human-readable format or perform translation. + */ + public static DisplayMode[] getAvailableModes() { + return new DisplayMode[0]; + } + + /** + * Get the name of the currently selected mode. This can return + * null if no mode is selected. + */ + public static DisplayMode getCurrentMode() { + return null; + } + + /** + * Selects a mode from the list of available modes by it's + * string identifier. Returns true on success, false for + * failure. It is up to the implementation to determine + * if this mode is valid. + */ + public static boolean setMode(DisplayMode mode, boolean makeDefault) { + return false; + } + + /** + * Gets the preferred default mode for this device by it's + * string identifier. Can return null if there is no default. + */ + public static DisplayMode getDefaultMode() { + return null; + } +} diff --git a/core/java/com/android/internal/custom/hardware/HIDLHelper.java b/core/java/com/android/internal/custom/hardware/HIDLHelper.java new file mode 100644 index 000000000000..490145f33c05 --- /dev/null +++ b/core/java/com/android/internal/custom/hardware/HIDLHelper.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2019 The LineageOS Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.custom.hardware; + +import android.util.Range; + +import com.android.internal.custom.hardware.DisplayMode; +import com.android.internal.custom.hardware.HSIC; + +import java.util.ArrayList; + +class HIDLHelper { + + static DisplayMode[] fromHIDLModes( + ArrayList modes) { + int size = modes.size(); + DisplayMode[] r = new DisplayMode[size]; + for (int i = 0; i < size; i++) { + vendor.lineage.livedisplay.V2_0.DisplayMode m = modes.get(i); + r[i] = new DisplayMode(m.id, m.name); + } + return r; + } + + static DisplayMode fromHIDLMode( + vendor.lineage.livedisplay.V2_0.DisplayMode mode) { + return new DisplayMode(mode.id, mode.name); + } + + static HSIC fromHIDLHSIC(vendor.lineage.livedisplay.V2_0.HSIC hsic) { + return new HSIC(hsic.hue, hsic.saturation, hsic.intensity, + hsic.contrast, hsic.saturationThreshold); + } + + static vendor.lineage.livedisplay.V2_0.HSIC toHIDLHSIC(HSIC hsic) { + vendor.lineage.livedisplay.V2_0.HSIC h = new vendor.lineage.livedisplay.V2_0.HSIC(); + h.hue = hsic.getHue(); + h.saturation = hsic.getSaturation(); + h.intensity = hsic.getIntensity(); + h.contrast = hsic.getContrast(); + h.saturationThreshold = hsic.getSaturationThreshold(); + return h; + } + + static Range fromHIDLRange(vendor.lineage.livedisplay.V2_0.Range range) { + return new Range(range.min, range.max); + } + + static Range fromHIDLRange(vendor.lineage.livedisplay.V2_0.FloatRange range) { + return new Range(range.min, range.max); + } + +} diff --git a/core/java/com/android/internal/custom/hardware/HSIC.aidl b/core/java/com/android/internal/custom/hardware/HSIC.aidl new file mode 100644 index 000000000000..721a3771f769 --- /dev/null +++ b/core/java/com/android/internal/custom/hardware/HSIC.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2015 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 com.android.internal.custom.hardware; + +parcelable HSIC; diff --git a/core/java/com/android/internal/custom/hardware/HSIC.java b/core/java/com/android/internal/custom/hardware/HSIC.java new file mode 100644 index 000000000000..92f033dc3434 --- /dev/null +++ b/core/java/com/android/internal/custom/hardware/HSIC.java @@ -0,0 +1,128 @@ +/* + * 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 com.android.internal.custom.hardware; + +import android.graphics.Color; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import java.util.Locale; + +public class HSIC implements Parcelable { + + private final float mHue; + private final float mSaturation; + private final float mIntensity; + private final float mContrast; + private final float mSaturationThreshold; + + public HSIC(float hue, float saturation, float intensity, + float contrast, float saturationThreshold) { + mHue = hue; + mSaturation = saturation; + mIntensity = intensity; + mContrast = contrast; + mSaturationThreshold = saturationThreshold; + } + + public float getHue() { + return mHue; + } + + public float getSaturation() { + return mSaturation; + } + + public float getIntensity() { + return mIntensity; + } + + public float getContrast() { + return mContrast; + } + + public float getSaturationThreshold() { + return mSaturationThreshold; + } + + public String flatten() { + return String.format(Locale.US, "%f|%f|%f|%f|%f", mHue, mSaturation, + mIntensity, mContrast, mSaturationThreshold); + } + + public static HSIC unflattenFrom(String flat) throws NumberFormatException { + final String[] unflat = TextUtils.split(flat, "\\|"); + if (unflat.length != 4 && unflat.length != 5) { + throw new NumberFormatException("Failed to unflatten HSIC values: " + flat); + } + return new HSIC(Float.parseFloat(unflat[0]), Float.parseFloat(unflat[1]), + Float.parseFloat(unflat[2]), Float.parseFloat(unflat[3]), + unflat.length == 5 ? Float.parseFloat(unflat[4]) : 0.0f); + } + + public int[] toRGB() { + final int c = Color.HSVToColor(toFloatArray()); + return new int[] { Color.red(c), Color.green(c), Color.blue(c) }; + } + + public float[] toFloatArray() { + return new float[] { mHue, mSaturation, mIntensity, mContrast, mSaturationThreshold }; + } + + public static HSIC fromFloatArray(float[] hsic) { + if (hsic.length == 5) { + return new HSIC(hsic[0], hsic[1], hsic[2], hsic[3], hsic[4]); + } else if (hsic.length == 4) { + return new HSIC(hsic[0], hsic[1], hsic[2], hsic[3], 0.0f); + } + return null; + } + + @Override + public String toString() { + return String.format(Locale.US, "HSIC={ hue=%f saturation=%f intensity=%f " + + "contrast=%f saturationThreshold=%f }", + mHue, mSaturation, mIntensity, mContrast, mSaturationThreshold); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + parcel.writeFloatArray(toFloatArray()); + } + + /** + * @hide + */ + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public HSIC createFromParcel(Parcel in) { + float[] fromParcel = new float[5]; + in.readFloatArray(fromParcel); + return HSIC.fromFloatArray(fromParcel); + } + + @Override + public HSIC[] newArray(int size) { + return new HSIC[size]; + } + }; +}; diff --git a/core/java/com/android/internal/custom/hardware/ILineageHardwareService.aidl b/core/java/com/android/internal/custom/hardware/ILineageHardwareService.aidl new file mode 100644 index 000000000000..4f4eadc4fcfc --- /dev/null +++ b/core/java/com/android/internal/custom/hardware/ILineageHardwareService.aidl @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2015-2016 The CyanogenMod Project + * 2017-2018 The LineageOS Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.custom.hardware; + +import com.android.internal.custom.hardware.DisplayMode; +import com.android.internal.custom.hardware.HSIC; + +/** @hide */ +interface ILineageHardwareService { + + int getSupportedFeatures(); + boolean get(int feature); + boolean set(int feature, boolean enable); + + int[] getDisplayColorCalibration(); + boolean setDisplayColorCalibration(in int[] rgb); + + boolean requireAdaptiveBacklightForSunlightEnhancement(); + + DisplayMode[] getDisplayModes(); + DisplayMode getCurrentDisplayMode(); + DisplayMode getDefaultDisplayMode(); + boolean setDisplayMode(in DisplayMode mode, boolean makeDefault); + + boolean isSunlightEnhancementSelfManaged(); + + int getColorBalanceMin(); + int getColorBalanceMax(); + int getColorBalance(); + boolean setColorBalance(int value); + + HSIC getPictureAdjustment(); + HSIC getDefaultPictureAdjustment(); + boolean setPictureAdjustment(in HSIC hsic); + float[] getPictureAdjustmentRanges(); +} diff --git a/core/java/com/android/internal/custom/hardware/ILiveDisplayService.aidl b/core/java/com/android/internal/custom/hardware/ILiveDisplayService.aidl new file mode 100644 index 000000000000..15efe0485681 --- /dev/null +++ b/core/java/com/android/internal/custom/hardware/ILiveDisplayService.aidl @@ -0,0 +1,56 @@ +/** + * 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 com.android.internal.custom.hardware; + +import com.android.internal.custom.hardware.HSIC; +import com.android.internal.custom.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); + + HSIC getPictureAdjustment(); + HSIC getDefaultPictureAdjustment(); + boolean setPictureAdjustment(in HSIC adj); + boolean isNight(); +} diff --git a/core/java/com/android/internal/custom/hardware/LineageHardwareManager.java b/core/java/com/android/internal/custom/hardware/LineageHardwareManager.java new file mode 100644 index 000000000000..a3b1107b4871 --- /dev/null +++ b/core/java/com/android/internal/custom/hardware/LineageHardwareManager.java @@ -0,0 +1,758 @@ +/* + * Copyright (C) 2015-2016 The CyanogenMod Project + * 2017-2019 The LineageOS Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.custom.hardware; + +import android.content.Context; +import android.hidl.base.V1_0.IBase; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.ArrayMap; +import android.util.Log; +import android.util.Range; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; + +import com.android.internal.custom.app.LineageContextConstants; +import com.android.internal.custom.hardware.DisplayMode; +import com.android.internal.custom.hardware.HIDLHelper; +import com.android.internal.custom.hardware.HSIC; + +import vendor.lineage.livedisplay.V2_0.IAdaptiveBacklight; +import vendor.lineage.livedisplay.V2_0.IAutoContrast; +import vendor.lineage.livedisplay.V2_0.IColorBalance; +import vendor.lineage.livedisplay.V2_0.IColorEnhancement; +import vendor.lineage.livedisplay.V2_0.IDisplayColorCalibration; +import vendor.lineage.livedisplay.V2_0.IDisplayModes; +import vendor.lineage.livedisplay.V2_0.IPictureAdjustment; +import vendor.lineage.livedisplay.V2_0.IReadingEnhancement; +import vendor.lineage.livedisplay.V2_0.ISunlightEnhancement; + +import java.io.UnsupportedEncodingException; +import java.lang.IllegalArgumentException; +import java.lang.reflect.Field; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.NoSuchElementException; + +/** + * Manages access to LineageOS hardware extensions + * + *

+ * This manager requires the HARDWARE_ABSTRACTION_ACCESS permission. + *

+ * To get the instance of this class, utilize LineageHardwareManager#getInstance(Context context) + */ +public final class LineageHardwareManager { + private static final String TAG = "LineageHardwareManager"; + + // The VisibleForTesting annotation is to ensure Proguard doesn't remove these + // fields, as they might be used via reflection. When the @Keep annotation in + // the support library is properly handled in the platform, we should change this. + + /** + * Adaptive backlight support (this refers to technologies like NVIDIA SmartDimmer, + * QCOM CABL or Samsung CABC) + */ + @VisibleForTesting + public static final int FEATURE_ADAPTIVE_BACKLIGHT = 0x1; + + /** + * Color enhancement support + */ + @VisibleForTesting + public static final int FEATURE_COLOR_ENHANCEMENT = 0x2; + + /** + * Display RGB color calibration + */ + @VisibleForTesting + public static final int FEATURE_DISPLAY_COLOR_CALIBRATION = 0x4; + + /** + * Increased display readability in bright light + */ + @VisibleForTesting + public static final int FEATURE_SUNLIGHT_ENHANCEMENT = 0x10; + + /** + * Auto contrast + */ + @VisibleForTesting + public static final int FEATURE_AUTO_CONTRAST = 0x20; + + /** + * Display modes + */ + @VisibleForTesting + public static final int FEATURE_DISPLAY_MODES = 0x100; + + /** + * Reading mode + */ + @VisibleForTesting + public static final int FEATURE_READING_ENHANCEMENT = 0x400; + + /** + * Color balance + */ + @VisibleForTesting + public static final int FEATURE_COLOR_BALANCE = 0x800; + + /** + * HSIC picture adjustment + */ + @VisibleForTesting + public static final int FEATURE_PICTURE_ADJUSTMENT = 0x1000; + + private static final List BOOLEAN_FEATURES = Arrays.asList( + FEATURE_ADAPTIVE_BACKLIGHT, + FEATURE_AUTO_CONTRAST, + FEATURE_COLOR_ENHANCEMENT, + FEATURE_SUNLIGHT_ENHANCEMENT, + FEATURE_READING_ENHANCEMENT + ); + + private static ILineageHardwareService sService; + private static LineageHardwareManager sLineageHardwareManagerInstance; + + private Context mContext; + + private final ArrayMap mDisplayModeMappings = new ArrayMap(); + private final boolean mFilterDisplayModes; + + // HIDL hals + private HashMap mHIDLMap = new HashMap(); + + /** + * @hide to prevent subclassing from outside of the framework + */ + private LineageHardwareManager(Context context) { + Context appContext = context.getApplicationContext(); + if (appContext != null) { + mContext = appContext; + } else { + mContext = context; + } + sService = getService(); + + if (!checkService()) { + Log.wtf(TAG, "Unable to get LineageHardwareService. The service either" + + " crashed, was not started, or the interface has been called to early in" + + " SystemServer init"); + } + + final String[] mappings = mContext.getResources().getStringArray( + com.android.internal.R.array.config_displayModeMappings); + if (mappings != null && mappings.length > 0) { + for (String mapping : mappings) { + String[] split = mapping.split(":"); + if (split.length == 2) { + mDisplayModeMappings.put(split[0], split[1]); + } + } + } + mFilterDisplayModes = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_filterDisplayModes); + } + + /** + * Get or create an instance of the {@link com.android.internal.custom.hardware.LineageHardwareManager} + * @param context + * @return {@link LineageHardwareManager} + */ + public static LineageHardwareManager getInstance(Context context) { + if (sLineageHardwareManagerInstance == null) { + sLineageHardwareManagerInstance = new LineageHardwareManager(context); + } + return sLineageHardwareManagerInstance; + } + + /** @hide */ + public static ILineageHardwareService getService() { + if (sService != null) { + return sService; + } + IBinder b = ServiceManager.getService(LineageContextConstants.LINEAGE_HARDWARE_SERVICE); + if (b != null) { + sService = ILineageHardwareService.Stub.asInterface(b); + return sService; + } + return null; + } + + /** + * Determine if a Lineage Hardware feature is supported on this device + * + * @param feature The Lineage Hardware feature to query + * + * @return true if the feature is supported, false otherwise. + */ + public boolean isSupported(int feature) { + return isSupportedHIDL(feature) || isSupportedLegacy(feature); + } + + private boolean isSupportedHIDL(int feature) { + if (!mHIDLMap.containsKey(feature)) { + mHIDLMap.put(feature, getHIDLService(feature)); + } + return mHIDLMap.get(feature) != null; + } + + private boolean isSupportedLegacy(int feature) { + try { + if (checkService()) { + return feature == (sService.getSupportedFeatures() & feature); + } + } catch (RemoteException e) { + } + return false; + } + + private IBase getHIDLService(int feature) { + try { + switch (feature) { + case FEATURE_ADAPTIVE_BACKLIGHT: + return IAdaptiveBacklight.getService(true); + case FEATURE_AUTO_CONTRAST: + return IAutoContrast.getService(true); + case FEATURE_COLOR_BALANCE: + return IColorBalance.getService(true); + case FEATURE_COLOR_ENHANCEMENT: + return IColorEnhancement.getService(true); + case FEATURE_DISPLAY_COLOR_CALIBRATION: + return IDisplayColorCalibration.getService(true); + case FEATURE_DISPLAY_MODES: + return IDisplayModes.getService(true); + case FEATURE_PICTURE_ADJUSTMENT: + return IPictureAdjustment.getService(true); + case FEATURE_READING_ENHANCEMENT: + return IReadingEnhancement.getService(true); + case FEATURE_SUNLIGHT_ENHANCEMENT: + return ISunlightEnhancement.getService(true); + } + } catch (NoSuchElementException | RemoteException e) { + } + return null; + } + + /** + * String version for preference constraints + * + * @hide + */ + public boolean isSupported(String feature) { + if (!feature.startsWith("FEATURE_")) { + return false; + } + try { + Field f = getClass().getField(feature); + if (f != null) { + return isSupported((int) f.get(null)); + } + } catch (NoSuchFieldException | IllegalAccessException e) { + Log.d(TAG, e.getMessage(), e); + } + + return false; + } + /** + * Determine if the given feature is enabled or disabled. + * + * Only used for features which have simple enable/disable controls. + * + * @param feature the Lineage Hardware feature to query + * + * @return true if the feature is enabled, false otherwise. + */ + public boolean get(int feature) { + if (!BOOLEAN_FEATURES.contains(feature)) { + throw new IllegalArgumentException(feature + " is not a boolean"); + } + + try { + if (isSupportedHIDL(feature)) { + IBase obj = mHIDLMap.get(feature); + switch (feature) { + case FEATURE_ADAPTIVE_BACKLIGHT: + IAdaptiveBacklight adaptiveBacklight = (IAdaptiveBacklight) obj; + return adaptiveBacklight.isEnabled(); + case FEATURE_AUTO_CONTRAST: + IAutoContrast autoContrast = (IAutoContrast) obj; + return autoContrast.isEnabled(); + case FEATURE_COLOR_ENHANCEMENT: + IColorEnhancement colorEnhancement = (IColorEnhancement) obj; + return colorEnhancement.isEnabled(); + case FEATURE_SUNLIGHT_ENHANCEMENT: + ISunlightEnhancement sunlightEnhancement = (ISunlightEnhancement) obj; + return sunlightEnhancement.isEnabled(); + case FEATURE_READING_ENHANCEMENT: + IReadingEnhancement readingEnhancement = (IReadingEnhancement) obj; + return readingEnhancement.isEnabled(); + } + } else if (checkService()) { + return sService.get(feature); + } + } catch (RemoteException e) { + } + return false; + } + + /** + * Enable or disable the given feature + * + * Only used for features which have simple enable/disable controls. + * + * @param feature the Lineage Hardware feature to set + * @param enable true to enable, false to disale + * + * @return true if the feature is enabled, false otherwise. + */ + public boolean set(int feature, boolean enable) { + if (!BOOLEAN_FEATURES.contains(feature)) { + throw new IllegalArgumentException(feature + " is not a boolean"); + } + + try { + if (isSupportedHIDL(feature)) { + IBase obj = mHIDLMap.get(feature); + switch (feature) { + case FEATURE_ADAPTIVE_BACKLIGHT: + IAdaptiveBacklight adaptiveBacklight = (IAdaptiveBacklight) obj; + return adaptiveBacklight.setEnabled(enable); + case FEATURE_AUTO_CONTRAST: + IAutoContrast autoContrast = (IAutoContrast) obj; + return autoContrast.setEnabled(enable); + case FEATURE_COLOR_ENHANCEMENT: + IColorEnhancement colorEnhancement = (IColorEnhancement) obj; + return colorEnhancement.setEnabled(enable); + case FEATURE_SUNLIGHT_ENHANCEMENT: + ISunlightEnhancement sunlightEnhancement = (ISunlightEnhancement) obj; + return sunlightEnhancement.setEnabled(enable); + case FEATURE_READING_ENHANCEMENT: + IReadingEnhancement readingEnhancement = (IReadingEnhancement) obj; + return readingEnhancement.setEnabled(enable); + } + } else if (checkService()) { + return sService.set(feature, enable); + } + } catch (RemoteException e) { + } + return false; + } + + private int getArrayValue(int[] arr, int idx, int defaultValue) { + if (arr == null || arr.length <= idx) { + return defaultValue; + } + + return arr[idx]; + } + + /** + * {@hide} + */ + public static final int COLOR_CALIBRATION_RED_INDEX = 0; + /** + * {@hide} + */ + public static final int COLOR_CALIBRATION_GREEN_INDEX = 1; + /** + * {@hide} + */ + public static final int COLOR_CALIBRATION_BLUE_INDEX = 2; + /** + * {@hide} + */ + public static final int COLOR_CALIBRATION_MIN_INDEX = 3; + /** + * {@hide} + */ + public static final int COLOR_CALIBRATION_MAX_INDEX = 4; + + private int[] getDisplayColorCalibrationArray() { + try { + if (isSupportedHIDL(FEATURE_DISPLAY_COLOR_CALIBRATION)) { + IDisplayColorCalibration displayColorCalibration = (IDisplayColorCalibration) + mHIDLMap.get(FEATURE_DISPLAY_COLOR_CALIBRATION); + return ArrayUtils.convertToIntArray(displayColorCalibration.getCalibration()); + } else if (checkService()) { + return sService.getDisplayColorCalibration(); + } + } catch (RemoteException e) { + } + return null; + } + + /** + * @return the current RGB calibration, where int[0] = R, int[1] = G, int[2] = B. + */ + public int[] getDisplayColorCalibration() { + int[] arr = getDisplayColorCalibrationArray(); + if (arr == null || arr.length < 3) { + return null; + } + return Arrays.copyOf(arr, 3); + } + + /** + * @return The minimum value for all colors + */ + public int getDisplayColorCalibrationMin() { + if (isSupportedHIDL(FEATURE_DISPLAY_COLOR_CALIBRATION)) { + IDisplayColorCalibration displayColorCalibration = (IDisplayColorCalibration) + mHIDLMap.get(FEATURE_DISPLAY_COLOR_CALIBRATION); + try { + return displayColorCalibration.getMinValue(); + } catch (RemoteException e) { + return 0; + } + } + + return getArrayValue(getDisplayColorCalibrationArray(), COLOR_CALIBRATION_MIN_INDEX, 0); + } + + /** + * @return The maximum value for all colors + */ + public int getDisplayColorCalibrationMax() { + if (isSupportedHIDL(FEATURE_DISPLAY_COLOR_CALIBRATION)) { + IDisplayColorCalibration displayColorCalibration = (IDisplayColorCalibration) + mHIDLMap.get(FEATURE_DISPLAY_COLOR_CALIBRATION); + try { + return displayColorCalibration.getMaxValue(); + } catch (RemoteException e) { + return 0; + } + } + + return getArrayValue(getDisplayColorCalibrationArray(), COLOR_CALIBRATION_MAX_INDEX, 0); + } + + /** + * Set the display color calibration to the given rgb triplet + * + * @param rgb RGB color calibration. Each value must be between + * {@link #getDisplayColorCalibrationMin()} and {@link #getDisplayColorCalibrationMax()}, + * inclusive. + * + * @return true on success, false otherwise. + */ + public boolean setDisplayColorCalibration(int[] rgb) { + try { + if (isSupportedHIDL(FEATURE_DISPLAY_COLOR_CALIBRATION)) { + IDisplayColorCalibration displayColorCalibration = (IDisplayColorCalibration) + mHIDLMap.get(FEATURE_DISPLAY_COLOR_CALIBRATION); + return displayColorCalibration.setCalibration( + new ArrayList(Arrays.asList(rgb[0], rgb[1], rgb[2]))); + } else if (checkService()) { + return sService.setDisplayColorCalibration(rgb); + } + } catch (RemoteException e) { + } + return false; + } + + /** + * @return true if adaptive backlight should be enabled when sunlight enhancement + * is enabled. + */ + public boolean requireAdaptiveBacklightForSunlightEnhancement() { + if (isSupportedHIDL(FEATURE_SUNLIGHT_ENHANCEMENT)) { + return false; + } + + try { + if (checkService()) { + return sService.requireAdaptiveBacklightForSunlightEnhancement(); + } + } catch (RemoteException e) { + } + return false; + } + + /** + * @return true if this implementation does it's own lux metering + */ + public boolean isSunlightEnhancementSelfManaged() { + if (isSupportedHIDL(FEATURE_SUNLIGHT_ENHANCEMENT)) { + return false; + } + + try { + if (checkService()) { + return sService.isSunlightEnhancementSelfManaged(); + } + } catch (RemoteException e) { + } + return false; + } + + /** + * @return a list of available display modes on the devices + */ + public DisplayMode[] getDisplayModes() { + DisplayMode[] modes = null; + try { + if (isSupportedHIDL(FEATURE_DISPLAY_MODES)) { + IDisplayModes displayModes = (IDisplayModes) mHIDLMap.get(FEATURE_DISPLAY_MODES); + modes = HIDLHelper.fromHIDLModes(displayModes.getDisplayModes()); + } else if (checkService()) { + modes= sService.getDisplayModes(); + } + } catch (RemoteException e) { + } finally { + if (modes == null) { + return null; + } + final ArrayList remapped = new ArrayList(); + for (DisplayMode mode : modes) { + DisplayMode r = remapDisplayMode(mode); + if (r != null) { + remapped.add(r); + } + } + return remapped.toArray(new DisplayMode[0]); + } + } + + /** + * @return the currently active display mode + */ + public DisplayMode getCurrentDisplayMode() { + DisplayMode mode = null; + try { + if (isSupportedHIDL(FEATURE_DISPLAY_MODES)) { + IDisplayModes displayModes = (IDisplayModes) mHIDLMap.get(FEATURE_DISPLAY_MODES); + mode = HIDLHelper.fromHIDLMode(displayModes.getCurrentDisplayMode()); + } else if (checkService()) { + mode = sService.getCurrentDisplayMode(); + } + } catch (RemoteException e) { + } finally { + return mode != null ? remapDisplayMode(mode) : null; + } + } + + /** + * @return the default display mode to be set on boot + */ + public DisplayMode getDefaultDisplayMode() { + DisplayMode mode = null; + try { + if (isSupportedHIDL(FEATURE_DISPLAY_MODES)) { + IDisplayModes displayModes = (IDisplayModes) mHIDLMap.get(FEATURE_DISPLAY_MODES); + mode = HIDLHelper.fromHIDLMode(displayModes.getDefaultDisplayMode()); + } else if (checkService()) { + mode = sService.getDefaultDisplayMode(); + } + } catch (RemoteException e) { + } finally { + return mode != null ? remapDisplayMode(mode) : null; + } + } + + /** + * @return true if setting the mode was successful + */ + public boolean setDisplayMode(DisplayMode mode, boolean makeDefault) { + try { + if (isSupportedHIDL(FEATURE_DISPLAY_MODES)) { + IDisplayModes displayModes = (IDisplayModes) mHIDLMap.get(FEATURE_DISPLAY_MODES); + return displayModes.setDisplayMode(mode.id, makeDefault); + } else if (checkService()) { + return sService.setDisplayMode(mode, makeDefault); + } + } catch (RemoteException e) { + } + return false; + } + + private DisplayMode remapDisplayMode(DisplayMode in) { + if (in == null) { + return null; + } + if (mDisplayModeMappings.containsKey(in.name)) { + return new DisplayMode(in.id, mDisplayModeMappings.get(in.name)); + } + if (!mFilterDisplayModes) { + return in; + } + return null; + } + + /** + * @return the available range for color temperature adjustments + */ + public Range getColorBalanceRange() { + try { + if (isSupportedHIDL(FEATURE_COLOR_BALANCE)) { + IColorBalance colorBalance = (IColorBalance) mHIDLMap.get(FEATURE_COLOR_BALANCE); + return HIDLHelper.fromHIDLRange(colorBalance.getColorBalanceRange()); + } else if (checkService()) { + return new Range( + sService.getColorBalanceMin(), + sService.getColorBalanceMax()); + } + } catch (RemoteException e) { + } + return new Range(0, 0); + } + + /** + * @return the current color balance value + */ + public int getColorBalance() { + try { + if (isSupportedHIDL(FEATURE_COLOR_BALANCE)) { + IColorBalance colorBalance = (IColorBalance) mHIDLMap.get(FEATURE_COLOR_BALANCE); + return colorBalance.getColorBalance(); + } else if (checkService()) { + return sService.getColorBalance(); + } + } catch (RemoteException e) { + } + return 0; + } + + /** + * Sets the desired color balance. Must fall within the range obtained from + * getColorBalanceRange() + * + * @param value + * @return true if success + */ + public boolean setColorBalance(int value) { + try { + if (isSupportedHIDL(FEATURE_COLOR_BALANCE)) { + IColorBalance colorBalance = (IColorBalance) mHIDLMap.get(FEATURE_COLOR_BALANCE); + return colorBalance.setColorBalance(value); + } else if (checkService()) { + return sService.setColorBalance(value); + } + } catch (RemoteException e) { + } + return false; + } + + /** + * Gets the current picture adjustment values + * + * @return HSIC object with current settings + */ + public HSIC getPictureAdjustment() { + try { + if (isSupportedHIDL(FEATURE_PICTURE_ADJUSTMENT)) { + IPictureAdjustment pictureAdjustment = (IPictureAdjustment) + mHIDLMap.get(FEATURE_PICTURE_ADJUSTMENT); + return HIDLHelper.fromHIDLHSIC(pictureAdjustment.getPictureAdjustment()); + } else if (checkService()) { + return sService.getPictureAdjustment(); + } + } catch (RemoteException e) { + } + return null; + } + + /** + * Gets the default picture adjustment for the current mode + * + * @return HSIC object with default settings + */ + public HSIC getDefaultPictureAdjustment() { + try { + if (isSupportedHIDL(FEATURE_PICTURE_ADJUSTMENT)) { + IPictureAdjustment pictureAdjustment = (IPictureAdjustment) + mHIDLMap.get(FEATURE_PICTURE_ADJUSTMENT); + return HIDLHelper.fromHIDLHSIC(pictureAdjustment.getDefaultPictureAdjustment()); + } else if (checkService()) { + return sService.getDefaultPictureAdjustment(); + } + } catch (RemoteException e) { + } + return null; + } + + /** + * Sets the desired hue/saturation/intensity/contrast + * + * @param hsic + * @return true if success + */ + public boolean setPictureAdjustment(final HSIC hsic) { + try { + if (isSupportedHIDL(FEATURE_PICTURE_ADJUSTMENT)) { + IPictureAdjustment pictureAdjustment = (IPictureAdjustment) + mHIDLMap.get(FEATURE_PICTURE_ADJUSTMENT); + return pictureAdjustment.setPictureAdjustment(HIDLHelper.toHIDLHSIC(hsic)); + } else if (checkService()) { + return sService.setPictureAdjustment(hsic); + } + } catch (RemoteException e) { + } + return false; + } + + /** + * Get a list of ranges valid for picture adjustment. + * + * @return range list + */ + public List> getPictureAdjustmentRanges() { + try { + if (isSupportedHIDL(FEATURE_PICTURE_ADJUSTMENT)) { + IPictureAdjustment pictureAdjustment = (IPictureAdjustment) + mHIDLMap.get(FEATURE_PICTURE_ADJUSTMENT); + return Arrays.asList( + HIDLHelper.fromHIDLRange(pictureAdjustment.getHueRange()), + HIDLHelper.fromHIDLRange(pictureAdjustment.getSaturationRange()), + HIDLHelper.fromHIDLRange(pictureAdjustment.getIntensityRange()), + HIDLHelper.fromHIDLRange(pictureAdjustment.getContrastRange()), + HIDLHelper.fromHIDLRange(pictureAdjustment.getSaturationThresholdRange())); + } else if (checkService()) { + float[] ranges = sService.getPictureAdjustmentRanges(); + if (ranges.length > 7) { + return Arrays.asList(new Range(ranges[0], ranges[1]), + new Range(ranges[2], ranges[3]), + new Range(ranges[4], ranges[5]), + new Range(ranges[6], ranges[7]), + (ranges.length > 9 ? + new Range(ranges[8], ranges[9]) : + new Range(0.0f, 0.0f))); + } + } + } catch (RemoteException e) { + } + return null; + } + + /** + * @return true if service is valid + */ + private boolean checkService() { + if (sService == null) { + Log.w(TAG, "not connected to LineageHardwareManagerService"); + return false; + } + return true; + } + +} diff --git a/core/java/com/android/internal/custom/hardware/LiveDisplayConfig.aidl b/core/java/com/android/internal/custom/hardware/LiveDisplayConfig.aidl new file mode 100644 index 000000000000..8a7a750cb8ef --- /dev/null +++ b/core/java/com/android/internal/custom/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 com.android.internal.custom.hardware; + +parcelable LiveDisplayConfig; diff --git a/core/java/com/android/internal/custom/hardware/LiveDisplayConfig.java b/core/java/com/android/internal/custom/hardware/LiveDisplayConfig.java new file mode 100644 index 000000000000..97b35e7d27e4 --- /dev/null +++ b/core/java/com/android/internal/custom/hardware/LiveDisplayConfig.java @@ -0,0 +1,386 @@ +/* + * 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 com.android.internal.custom.hardware; + +import static com.android.internal.custom.hardware.LiveDisplayManager.FEATURE_COLOR_BALANCE; +import static com.android.internal.custom.hardware.LiveDisplayManager.FEATURE_FIRST; +import static com.android.internal.custom.hardware.LiveDisplayManager.FEATURE_LAST; +import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_FIRST; +import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_LAST; +import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_OFF; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Range; + +import java.util.Arrays; +import java.util.BitSet; +import java.util.List; + +import com.android.internal.util.custom.Concierge; +import com.android.internal.util.custom.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 BitSet mAllModes = new BitSet(); + + 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; + + private final Range mColorTemperatureRange; + private final Range mColorBalanceRange; + private final Range mHueRange; + private final Range mSaturationRange; + private final Range mIntensityRange; + private final Range mContrastRange; + private final Range mSaturationThresholdRange; + + public LiveDisplayConfig(BitSet capabilities, int defaultMode, + int defaultDayTemperature, int defaultNightTemperature, + boolean defaultAutoOutdoorMode, boolean defaultAutoContrast, + boolean defaultCABC, boolean defaultColorEnhancement, + Range colorTemperatureRange, + Range colorBalanceRange, + Range hueRange, + Range saturationRange, + Range intensityRange, + Range contrastRange, + Range saturationThresholdRange) { + super(); + mCapabilities = (BitSet) capabilities.clone(); + mAllModes.set(MODE_FIRST, MODE_LAST); + mDefaultMode = defaultMode; + mDefaultDayTemperature = defaultDayTemperature; + mDefaultNightTemperature = defaultNightTemperature; + mDefaultAutoContrast = defaultAutoContrast; + mDefaultAutoOutdoorMode = defaultAutoOutdoorMode; + mDefaultCABC = defaultCABC; + mDefaultColorEnhancement = defaultColorEnhancement; + mColorTemperatureRange = colorTemperatureRange; + mColorBalanceRange = colorBalanceRange; + mHueRange = hueRange; + mSaturationRange = saturationRange; + mIntensityRange = intensityRange; + mContrastRange = contrastRange; + mSaturationThresholdRange = saturationThresholdRange; + } + + 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; + int minColorTemperature = 0; + int maxColorTemperature = 0; + int minColorBalance = 0; + int maxColorBalance = 0; + float[] paRanges = new float[10]; + + 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; + minColorTemperature = parcel.readInt(); + maxColorTemperature = parcel.readInt(); + minColorBalance = parcel.readInt(); + maxColorBalance = parcel.readInt(); + parcel.readFloatArray(paRanges); + + // set temps + mCapabilities = BitSet.valueOf(new long[] { capabilities }); + mAllModes.set(MODE_FIRST, MODE_LAST); + mDefaultMode = defaultMode; + mDefaultDayTemperature = defaultDayTemperature; + mDefaultNightTemperature = defaultNightTemperature; + mDefaultAutoContrast = defaultAutoContrast; + mDefaultAutoOutdoorMode = defaultAutoOutdoorMode; + mDefaultCABC = defaultCABC; + mDefaultColorEnhancement = defaultColorEnhancement; + mColorTemperatureRange = Range.create(minColorTemperature, maxColorTemperature); + mColorBalanceRange = Range.create(minColorBalance, maxColorBalance); + mHueRange = Range.create(paRanges[0], paRanges[1]); + mSaturationRange = Range.create(paRanges[2], paRanges[3]); + mIntensityRange = Range.create(paRanges[4], paRanges[5]); + mContrastRange = Range.create(paRanges[6], paRanges[7]); + mSaturationThresholdRange = Range.create(paRanges[8], paRanges[9]); + + // 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); + sb.append(" colorTemperatureRange=").append(mColorTemperatureRange); + if (mCapabilities.get(LiveDisplayManager.FEATURE_COLOR_BALANCE)) { + sb.append(" colorBalanceRange=").append(mColorBalanceRange); + } + if (mCapabilities.get(LiveDisplayManager.FEATURE_PICTURE_ADJUSTMENT)) { + sb.append(" hueRange=").append(mHueRange); + sb.append(" saturationRange=").append(mSaturationRange); + sb.append(" intensityRange=").append(mIntensityRange); + sb.append(" contrastRange=").append(mContrastRange); + sb.append(" saturationThresholdRange=").append(mSaturationThresholdRange); + } + 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 ===== + long[] caps = mCapabilities.toLongArray(); + out.writeLong(caps != null && caps.length > 0 ? caps[0] : 0L); + 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); + out.writeInt(mColorTemperatureRange.getLower()); + out.writeInt(mColorTemperatureRange.getUpper()); + out.writeInt(mColorBalanceRange.getLower()); + out.writeInt(mColorBalanceRange.getUpper()); + out.writeFloatArray(new float[] { + mHueRange.getLower(), mHueRange.getUpper(), + mSaturationRange.getLower(), mSaturationRange.getUpper(), + mIntensityRange.getLower(), mIntensityRange.getUpper(), + mContrastRange.getLower(), mContrastRange.getUpper(), + mSaturationThresholdRange.getLower(), mSaturationThresholdRange.getUpper() } ); + + // 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(); + } + + /** + * Checks if LiveDisplay has support for adaptive modes. + * + * @return true if adaptive modes are available + */ + public boolean hasModeSupport() { + return isAvailable() && mCapabilities.intersects(mAllModes); + } + + /** + * 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; + } + + /** + * Get the range of supported color temperatures + * + * @return range in Kelvin + */ + public Range getColorTemperatureRange() { + return mColorTemperatureRange; + } + + /** + * Get the range of supported color balance + * + * @return linear range which maps into the temperature range curve + */ + public Range getColorBalanceRange() { + return mColorBalanceRange; + } + + /** + * Get the supported range for hue adjustment + * + * @return float range + */ + public Range getHueRange() { return mHueRange; } + + /** + * Get the supported range for saturation adjustment + * + * @return float range + */ + public Range getSaturationRange() { return mSaturationRange; } + + /** + * Get the supported range for intensity adjustment + * + * @return float range + */ + public Range getIntensityRange() { return mIntensityRange; } + + /** + * Get the supported range for contrast adjustment + * + * @return float range + */ + public Range getContrastRange() { return mContrastRange; } + + /** + * Get the supported range for saturation threshold adjustment + * + * @return float range + */ + public Range getSaturationThresholdRange() { + return mSaturationThresholdRange; + } + + + /** + * Convenience method to get a list of all picture adjustment ranges + * with a single call. + * + * @return List of float ranges + */ + public List> getPictureAdjustmentRanges() { + return Arrays.asList(mHueRange, mSaturationRange, mIntensityRange, + mContrastRange, mSaturationThresholdRange); + } + + /** @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/core/java/com/android/internal/custom/hardware/LiveDisplayManager.java b/core/java/com/android/internal/custom/hardware/LiveDisplayManager.java new file mode 100644 index 000000000000..ce5f2cbdebaa --- /dev/null +++ b/core/java/com/android/internal/custom/hardware/LiveDisplayManager.java @@ -0,0 +1,505 @@ +/* + * Copyright (C) 2016 The CyanogenMod Project + * 2018 The LineageOS Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.custom.hardware; + +import android.content.Context; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; +import android.util.Range; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import com.android.internal.custom.app.LineageContextConstants; + +/** + * 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 LineageHardwareService 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; + + /** + * System supports direct range-based control of display + * color balance (temperature). This is preferred over + * simple RGB adjustment. + */ + public static final int FEATURE_COLOR_BALANCE = 16; + + /** + * System supports manual hue/saturation/intensity/contrast + * adjustment of display. + */ + public static final int FEATURE_PICTURE_ADJUSTMENT = 17; + + /** + * System supports grayscale matrix overlay + */ + public static final int FEATURE_READING_ENHANCEMENT = 18; + + public static final int ADJUSTMENT_HUE = 0; + public static final int ADJUSTMENT_SATURATION = 1; + public static final int ADJUSTMENT_INTENSITY = 2; + public static final int ADJUSTMENT_CONTRAST = 3; + + /** @hide */ + public static final int FEATURE_FIRST = FEATURE_CABC; + /** @hide */ + public static final int FEATURE_LAST = FEATURE_PICTURE_ADJUSTMENT; + + 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 (!checkService()) { + Log.wtf(TAG, "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(); + if (mConfig == null) { + Log.w(TAG, "Unable to get LiveDisplay configuration!"); + } + } catch (RemoteException e) { + throw new RuntimeException("Unable to fetch LiveDisplay configuration!", e); + } + } + + /** + * Get or create an instance of the {@link com.android.internal.custom.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(LineageContextConstants.LINEAGE_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 LineageHardwareManagerService"); + 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; + } + } + + /** + * Gets the current picture adjustment settings (hue, saturation, intensity, contrast) + * + * @return HSIC object with current settings + */ + public HSIC getPictureAdjustment() { + try { + if (checkService()) { + return sService.getPictureAdjustment(); + } + } catch (RemoteException e) { + } + return null; + } + + /** + * Sets a new picture adjustment + * + * @param hsic + * @return true if success + */ + public boolean setPictureAdjustment(final HSIC hsic) { + try { + return checkService() && sService.setPictureAdjustment(hsic); + } catch (RemoteException e) { + } + return false; + } + + /** + * Gets the default picture adjustment for the current display mode + * + * @return HSIC object with default values + */ + public HSIC getDefaultPictureAdjustment() { + try { + if (checkService()) { + return sService.getDefaultPictureAdjustment(); + } + } catch (RemoteException e) { + } + return null; + } + + /** + * Determine whether night mode is enabled (be it automatic or manual) + */ + public boolean isNightModeEnabled() { + // This method might be called before config has been set up + // so a NPE would have been thrown, just report night mode is disabled instead + try { + return getMode() == MODE_NIGHT || sService.isNight(); + } catch (NullPointerException e) { + Log.w(TAG, "Can\'t check whether night mode is enabled because the service isn\'t ready"); + } catch (RemoteException ignored) { + } + return false; + } +} diff --git a/core/java/com/android/internal/custom/hardware/PictureAdjustment.java b/core/java/com/android/internal/custom/hardware/PictureAdjustment.java new file mode 100644 index 000000000000..302747a719ba --- /dev/null +++ b/core/java/com/android/internal/custom/hardware/PictureAdjustment.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2016 The CyanogenMod Project + * Copyright (C) 2018 The LineageOS Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.custom.hardware; + +import android.util.Range; + +import com.android.internal.custom.hardware.HSIC; + +/** + * Picture adjustment support + * + * Allows tuning of hue, saturation, intensity, and contrast levels + * of the display + */ +public class PictureAdjustment { + + /** + * Whether device supports picture adjustment + * + * @return boolean Supported devices must return always true + */ + public static boolean isSupported() { + return false; + } + + /** + * This method returns the current picture adjustment values based + * on the selected DisplayMode. + * + * @return the HSIC object or null if not supported + */ + public static HSIC getHSIC() { + return null; + } + + /** + * This method returns the default picture adjustment values. + * + * If DisplayModes are available, this may change depending on the + * selected mode. + * + * @return the HSIC object or null if not supported + */ + public static HSIC getDefaultHSIC() { + return null; + } + + /** + * This method allows to set the picture adjustment + * + * @param hsic + * @return boolean Must be false if feature is not supported or the operation + * failed; true in any other case. + */ + public static boolean setHSIC(final HSIC hsic) { + return false; + } + + /** + * Get the range available for hue adjustment + * @return range of floats + */ + public static Range getHueRange() { + return new Range(0.0f, 0.0f); + } + + /** + * Get the range available for saturation adjustment + * @return range of floats + */ + public static Range getSaturationRange() { + return new Range(0.0f, 0.0f); + } + + /** + * Get the range available for intensity adjustment + * @return range of floats + */ + public static Range getIntensityRange() { + return new Range(0.0f, 0.0f); + } + + /** + * Get the range available for contrast adjustment + * @return range of floats + */ + public static Range getContrastRange() { + return new Range(0.0f, 0.0f); + } + + /** + * Get the range available for saturation threshold adjustment + * + * This is the threshold where the display becomes fully saturated + * + * @return range of floats + */ + public static Range getSaturationThresholdRange() { + return new Range(0.0f, 0.0f); + } +} diff --git a/core/java/com/android/internal/custom/hardware/ReadingEnhancement.java b/core/java/com/android/internal/custom/hardware/ReadingEnhancement.java new file mode 100644 index 000000000000..1b1892c531b1 --- /dev/null +++ b/core/java/com/android/internal/custom/hardware/ReadingEnhancement.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2018 The LineageOS Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.custom.hardware; + +import com.android.internal.util.custom.FileUtils; + +/** + * Reader mode + */ +public class ReadingEnhancement { + + private static final String TAG = "ReadingEnhancement"; + + private static final String FILE_READING = "/sys/class/graphics/fb0/reading_mode"; + + /** + * Whether device supports Reader Mode + * + * @return boolean Supported devices must return always true + */ + public static boolean isSupported() { + return FileUtils.isFileReadable(FILE_READING) && FileUtils.isFileWritable(FILE_READING); + } + + /** + * This method return the current activation status of Reader Mode + * + * @return boolean Must be false when Reader Mode is not supported or not activated, + * or the operation failed while reading the status; true in any other case. + */ + public static boolean isEnabled() { + return Integer.parseInt(FileUtils.readOneLine(FILE_READING)) > 0; + } + + /** + * This method allows to setup Reader Mode + * + * @param status The new Reader Mode status + * @return boolean Must be false if Reader Mode is not supported or the operation + * failed; true in any other case. + */ + public static boolean setEnabled(boolean status) { + return FileUtils.writeLine(FILE_READING, status ? "1" : "0"); + } + +} diff --git a/core/java/com/android/internal/custom/hardware/SunlightEnhancement.java b/core/java/com/android/internal/custom/hardware/SunlightEnhancement.java new file mode 100644 index 000000000000..c22c6672b862 --- /dev/null +++ b/core/java/com/android/internal/custom/hardware/SunlightEnhancement.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * Copyright (C) 2018 The LineageOS Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.custom.hardware; + +import android.util.Log; + +import com.android.internal.util.custom.FileUtils; + +/** + * Facemelt mode! + */ +public class SunlightEnhancement { + + private static final String TAG = "SunlightEnhancement"; + + private static final String FACEMELT_PATH = getFacemeltPath(); + private static final String FACEMELT_MODE = getFacemeltMode(); + + private static final String FILE_HBM = "/sys/class/graphics/fb0/hbm"; + private static final String FILE_SRE = "/sys/class/graphics/fb0/sre"; + + private static String getFacemeltPath() { + if (FileUtils.fileExists(FILE_HBM)) { + return FILE_HBM; + } else { + return FILE_SRE; + } + } + + private static String getFacemeltMode() { + if (FileUtils.fileExists(FILE_HBM)) { + return "1"; + } else { + return "2"; + } + } + + /** + * Whether device supports sunlight enhancement + * + * @return boolean Supported devices must return always true + */ + public static boolean isSupported() { + return FileUtils.isFileReadable(FACEMELT_PATH) && FileUtils.isFileWritable(FACEMELT_PATH); + } + + /** + * This method return the current activation status of sunlight enhancement + * + * @return boolean Must be false when sunlight enhancement is not supported or not activated, + * or the operation failed while reading the status; true in any other case. + */ + public static boolean isEnabled() { + return Integer.parseInt(FileUtils.readOneLine(FACEMELT_PATH)) > 0; + } + + /** + * This method allows to setup sunlight enhancement + * + * @param status The new sunlight enhancement status + * @return boolean Must be false if sunlight enhancement is not supported or the operation + * failed; true in any other case. + */ + public static boolean setEnabled(boolean status) { + return FileUtils.writeLine(FACEMELT_PATH, status ? FACEMELT_MODE : "0"); + } + + /** + * Whether adaptive backlight (CABL / CABC) is required to be enabled + * + * @return boolean False if adaptive backlight is not a dependency + */ + public static boolean isAdaptiveBacklightRequired() { + return false; + } + + /** + * Set this to true if the implementation is self-managed and does + * it's own ambient sensing. In this case, setEnabled is assumed + * to toggle the feature on or off, but not activate it. If set + * to false, LiveDisplay will call setEnabled when the ambient lux + * threshold is crossed. + * + * @return true if this enhancement is self-managed + */ + public static boolean isSelfManaged() { + return false; + } +} diff --git a/core/java/com/android/internal/util/custom/ColorUtils.java b/core/java/com/android/internal/util/custom/ColorUtils.java new file mode 100644 index 000000000000..6167df9fb743 --- /dev/null +++ b/core/java/com/android/internal/util/custom/ColorUtils.java @@ -0,0 +1,532 @@ +/* + * Copyright (c) 2011-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 com.android.internal.util.custom; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.util.MathUtils; + +import com.android.internal.util.custom.palette.Palette; + +import java.util.Collections; +import java.util.Comparator; + +/** + * Helper class for colorspace conversions, and color-related + * algorithms which may be generally useful. + */ +public class ColorUtils { + + private static int[] SOLID_COLORS = new int[] { + Color.RED, 0xFFFFA500, Color.YELLOW, Color.GREEN, Color.CYAN, + Color.BLUE, Color.MAGENTA, Color.WHITE, Color.BLACK + }; + + /** + * Drop the alpha component from an RGBA packed int and return + * a non sign-extended RGB int. + * + * @param rgba + * @return rgb + */ + public static int dropAlpha(int rgba) { + return rgba & 0x00FFFFFF; + } + + /** + * Converts an RGB packed int into L*a*b space, which is well-suited for finding + * perceptual differences in color + * + * @param rgb A 32-bit value of packed RGB ints + * @return array of Lab values of size 3 + */ + public static float[] convertRGBtoLAB(int rgb) { + float[] lab = new float[3]; + float fx, fy, fz; + float eps = 216.f / 24389.f; + float k = 24389.f / 27.f; + + float Xr = 0.964221f; // reference white D50 + float Yr = 1.0f; + float Zr = 0.825211f; + + // RGB to XYZ + float r = Color.red(rgb) / 255.f; //R 0..1 + float g = Color.green(rgb) / 255.f; //G 0..1 + float b = Color.blue(rgb) / 255.f; //B 0..1 + + // assuming sRGB (D65) + if (r <= 0.04045) + r = r / 12; + else + r = (float) Math.pow((r + 0.055) / 1.055, 2.4); + + if (g <= 0.04045) + g = g / 12; + else + g = (float) Math.pow((g + 0.055) / 1.055, 2.4); + + if (b <= 0.04045) + b = b / 12; + else + b = (float) Math.pow((b + 0.055) / 1.055, 2.4); + + float X = 0.436052025f * r + 0.385081593f * g + 0.143087414f * b; + float Y = 0.222491598f * r + 0.71688606f * g + 0.060621486f * b; + float Z = 0.013929122f * r + 0.097097002f * g + 0.71418547f * b; + + // XYZ to Lab + float xr = X / Xr; + float yr = Y / Yr; + float zr = Z / Zr; + + if (xr > eps) + fx = (float) Math.pow(xr, 1 / 3.); + else + fx = (float) ((k * xr + 16.) / 116.); + + if (yr > eps) + fy = (float) Math.pow(yr, 1 / 3.); + else + fy = (float) ((k * yr + 16.) / 116.); + + if (zr > eps) + fz = (float) Math.pow(zr, 1 / 3.); + else + fz = (float) ((k * zr + 16.) / 116); + + float Ls = (116 * fy) - 16; + float as = 500 * (fx - fy); + float bs = 200 * (fy - fz); + + lab[0] = (2.55f * Ls + .5f); + lab[1] = (as + .5f); + lab[2] = (bs + .5f); + + return lab; + } + + /** + * Calculate the colour difference value between two colours in lab space. + * This code is from OpenIMAJ under BSD License + * + * @param L1 first colour's L component + * @param a1 first colour's a component + * @param b1 first colour's b component + * @param L2 second colour's L component + * @param a2 second colour's a component + * @param b2 second colour's b component + * @return the CIE 2000 colour difference + */ + public static double calculateDeltaE(double L1, double a1, double b1, + double L2, double a2, double b2) { + double Lmean = (L1 + L2) / 2.0; + double C1 = Math.sqrt(a1 * a1 + b1 * b1); + double C2 = Math.sqrt(a2 * a2 + b2 * b2); + double Cmean = (C1 + C2) / 2.0; + + double G = (1 - Math.sqrt(Math.pow(Cmean, 7) / (Math.pow(Cmean, 7) + Math.pow(25, 7)))) / 2; + double a1prime = a1 * (1 + G); + double a2prime = a2 * (1 + G); + + double C1prime = Math.sqrt(a1prime * a1prime + b1 * b1); + double C2prime = Math.sqrt(a2prime * a2prime + b2 * b2); + double Cmeanprime = (C1prime + C2prime) / 2; + + double h1prime = Math.atan2(b1, a1prime) + + 2 * Math.PI * (Math.atan2(b1, a1prime) < 0 ? 1 : 0); + double h2prime = Math.atan2(b2, a2prime) + + 2 * Math.PI * (Math.atan2(b2, a2prime) < 0 ? 1 : 0); + double Hmeanprime = ((Math.abs(h1prime - h2prime) > Math.PI) + ? (h1prime + h2prime + 2 * Math.PI) / 2 : (h1prime + h2prime) / 2); + + double T = 1.0 - 0.17 * Math.cos(Hmeanprime - Math.PI / 6.0) + + 0.24 * Math.cos(2 * Hmeanprime) + 0.32 * Math.cos(3 * Hmeanprime + Math.PI / 30) + - 0.2 * Math.cos(4 * Hmeanprime - 21 * Math.PI / 60); + + double deltahprime = ((Math.abs(h1prime - h2prime) <= Math.PI) ? h2prime - h1prime + : (h2prime <= h1prime) ? h2prime - h1prime + 2 * Math.PI + : h2prime - h1prime - 2 * Math.PI); + + double deltaLprime = L2 - L1; + double deltaCprime = C2prime - C1prime; + double deltaHprime = 2.0 * Math.sqrt(C1prime * C2prime) * Math.sin(deltahprime / 2.0); + double SL = 1.0 + ((0.015 * (Lmean - 50) * (Lmean - 50)) + / (Math.sqrt(20 + (Lmean - 50) * (Lmean - 50)))); + double SC = 1.0 + 0.045 * Cmeanprime; + double SH = 1.0 + 0.015 * Cmeanprime * T; + + double deltaTheta = (30 * Math.PI / 180) + * Math.exp(-((180 / Math.PI * Hmeanprime - 275) / 25) + * ((180 / Math.PI * Hmeanprime - 275) / 25)); + double RC = (2 + * Math.sqrt(Math.pow(Cmeanprime, 7) / (Math.pow(Cmeanprime, 7) + Math.pow(25, 7)))); + double RT = (-RC * Math.sin(2 * deltaTheta)); + + double KL = 1; + double KC = 1; + double KH = 1; + + double deltaE = Math.sqrt( + ((deltaLprime / (KL * SL)) * (deltaLprime / (KL * SL))) + + ((deltaCprime / (KC * SC)) * (deltaCprime / (KC * SC))) + + ((deltaHprime / (KH * SH)) * (deltaHprime / (KH * SH))) + + (RT * (deltaCprime / (KC * SC)) * (deltaHprime / (KH * SH)))); + + return deltaE; + } + + /** + * Finds the "perceptually nearest" color from a list of colors to + * the given RGB value. This is done by converting to + * L*a*b colorspace and using the CIE2000 deltaE algorithm. + * + * @param rgb The original color to start with + * @param colors An array of colors to test + * @return RGB packed int of nearest color in the list + */ + public static int findPerceptuallyNearestColor(int rgb, int[] colors) { + int nearestColor = 0; + double closest = Double.MAX_VALUE; + + float[] original = convertRGBtoLAB(rgb); + + for (int i = 0; i < colors.length; i++) { + float[] cl = convertRGBtoLAB(colors[i]); + double deltaE = calculateDeltaE(original[0], original[1], original[2], + cl[0], cl[1], cl[2]); + if (deltaE < closest) { + nearestColor = colors[i]; + closest = deltaE; + } + } + return nearestColor; + } + + /** + * Convenience method to find the nearest "solid" color (having RGB components + * of either 0 or 255) to the given color. This is useful for cases such as + * LED notification lights which may not be able to display the full range + * of colors due to hardware limitations. + * + * @param rgb + * @return the perceptually nearest color in RGB + */ + public static int findPerceptuallyNearestSolidColor(int rgb) { + return findPerceptuallyNearestColor(rgb, SOLID_COLORS); + } + + /** + * Given a Palette, pick out the dominant swatch based on population + * + * @param palette + * @return the dominant Swatch + */ + public static Palette.Swatch getDominantSwatch(Palette palette) { + if (palette == null || palette.getSwatches().size() == 0) { + return null; + } + // find most-represented swatch based on population + return Collections.max(palette.getSwatches(), new Comparator() { + @Override + public int compare(Palette.Swatch sw1, Palette.Swatch sw2) { + return Integer.compare(sw1.getPopulation(), sw2.getPopulation()); + } + }); + } + + /** + * Takes a drawable and uses Palette to generate a suitable "alert" + * color which can be used for an external notification mechanism + * such as an RGB LED. This will always pick a solid color having + * RGB components of 255 or 0. + * + * @param drawable The drawable to generate a color for + * @return a suitable solid color which corresponds to the image + */ + public static int generateAlertColorFromDrawable(Drawable drawable) { + int alertColor = Color.BLACK; + Bitmap bitmap = null; + + if (drawable == null) { + return alertColor; + } + + if (drawable instanceof BitmapDrawable) { + bitmap = ((BitmapDrawable) drawable).getBitmap(); + } else { + int width = drawable.getIntrinsicWidth(); + int height = drawable.getIntrinsicHeight(); + bitmap = Bitmap.createBitmap(Math.max(1, width), + Math.max(1, height), + Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + } + + if (bitmap != null) { + Palette p = Palette.from(bitmap).generate(); + if (p == null) { + return alertColor; + } + + // First try the dominant color + final Palette.Swatch dominantSwatch = getDominantSwatch(p); + int iconColor = alertColor; + if (dominantSwatch != null) { + iconColor = dominantSwatch.getRgb(); + alertColor = findPerceptuallyNearestSolidColor(iconColor); + } + + // Try the most saturated color if we got white or black (boring) + if (alertColor == Color.BLACK || alertColor == Color.WHITE) { + iconColor = p.getVibrantColor(Color.WHITE); + alertColor = findPerceptuallyNearestSolidColor(iconColor); + } + + if (!(drawable instanceof BitmapDrawable)) { + bitmap.recycle(); + } + } + + return alertColor; + } + + /** + * Convert a color temperature value (in Kelvin) to a RGB units as floats. + * This can be used in a transform matrix or hardware gamma control. + * + * @param degreesK + * @return array of floats representing rgb values 0->1 + */ + public static float[] temperatureToRGB(int degreesK) { + int k = MathUtils.constrain(degreesK, 1000, 20000); + float a = (k % 100) / 100.0f; + int i = ((k - 1000)/ 100) * 3; + + return new float[] { interp(i, a), interp(i+1, a), interp(i+2, a) }; + } + + private static float interp(int i, float a) { + return MathUtils.lerp((float)sColorTable[i], (float)sColorTable[i+3], a); + } + + /** + * This table is a modified version of the original blackbody chart, found here: + * http://www.vendian.org/mncharity/dir3/blackbody/UnstableURLs/bbr_color.html + * + * Created by Ingo Thiel. + */ + private static final double[] sColorTable = new double[] { + 1.00000000, 0.18172716, 0.00000000, + 1.00000000, 0.25503671, 0.00000000, + 1.00000000, 0.30942099, 0.00000000, + 1.00000000, 0.35357379, 0.00000000, + 1.00000000, 0.39091524, 0.00000000, + 1.00000000, 0.42322816, 0.00000000, + 1.00000000, 0.45159884, 0.00000000, + 1.00000000, 0.47675916, 0.00000000, + 1.00000000, 0.49923747, 0.00000000, + 1.00000000, 0.51943421, 0.00000000, + 1.00000000, 0.54360078, 0.08679949, + 1.00000000, 0.56618736, 0.14065513, + 1.00000000, 0.58734976, 0.18362641, + 1.00000000, 0.60724493, 0.22137978, + 1.00000000, 0.62600248, 0.25591950, + 1.00000000, 0.64373109, 0.28819679, + 1.00000000, 0.66052319, 0.31873863, + 1.00000000, 0.67645822, 0.34786758, + 1.00000000, 0.69160518, 0.37579588, + 1.00000000, 0.70602449, 0.40267128, + 1.00000000, 0.71976951, 0.42860152, + 1.00000000, 0.73288760, 0.45366838, + 1.00000000, 0.74542112, 0.47793608, + 1.00000000, 0.75740814, 0.50145662, + 1.00000000, 0.76888303, 0.52427322, + 1.00000000, 0.77987699, 0.54642268, + 1.00000000, 0.79041843, 0.56793692, + 1.00000000, 0.80053332, 0.58884417, + 1.00000000, 0.81024551, 0.60916971, + 1.00000000, 0.81957693, 0.62893653, + 1.00000000, 0.82854786, 0.64816570, + 1.00000000, 0.83717703, 0.66687674, + 1.00000000, 0.84548188, 0.68508786, + 1.00000000, 0.85347859, 0.70281616, + 1.00000000, 0.86118227, 0.72007777, + 1.00000000, 0.86860704, 0.73688797, + 1.00000000, 0.87576611, 0.75326132, + 1.00000000, 0.88267187, 0.76921169, + 1.00000000, 0.88933596, 0.78475236, + 1.00000000, 0.89576933, 0.79989606, + 1.00000000, 0.90198230, 0.81465502, + 1.00000000, 0.90963069, 0.82838210, + 1.00000000, 0.91710889, 0.84190889, + 1.00000000, 0.92441842, 0.85523742, + 1.00000000, 0.93156127, 0.86836903, + 1.00000000, 0.93853986, 0.88130458, + 1.00000000, 0.94535695, 0.89404470, + 1.00000000, 0.95201559, 0.90658983, + 1.00000000, 0.95851906, 0.91894041, + 1.00000000, 0.96487079, 0.93109690, + 1.00000000, 0.97107439, 0.94305985, + 1.00000000, 0.97713351, 0.95482993, + 1.00000000, 0.98305189, 0.96640795, + 1.00000000, 0.98883326, 0.97779486, + 1.00000000, 0.99448139, 0.98899179, + 1.00000000, 1.00000000, 1.00000000, + 0.98947904, 0.99348723, 1.00000000, + 0.97940448, 0.98722715, 1.00000000, + 0.96975025, 0.98120637, 1.00000000, + 0.96049223, 0.97541240, 1.00000000, + 0.95160805, 0.96983355, 1.00000000, + 0.94303638, 0.96443333, 1.00000000, + 0.93480451, 0.95923080, 1.00000000, + 0.92689056, 0.95421394, 1.00000000, + 0.91927697, 0.94937330, 1.00000000, + 0.91194747, 0.94470005, 1.00000000, + 0.90488690, 0.94018594, 1.00000000, + 0.89808115, 0.93582323, 1.00000000, + 0.89151710, 0.93160469, 1.00000000, + 0.88518247, 0.92752354, 1.00000000, + 0.87906581, 0.92357340, 1.00000000, + 0.87315640, 0.91974827, 1.00000000, + 0.86744421, 0.91604254, 1.00000000, + 0.86191983, 0.91245088, 1.00000000, + 0.85657444, 0.90896831, 1.00000000, + 0.85139976, 0.90559011, 1.00000000, + 0.84638799, 0.90231183, 1.00000000, + 0.84153180, 0.89912926, 1.00000000, + 0.83682430, 0.89603843, 1.00000000, + 0.83225897, 0.89303558, 1.00000000, + 0.82782969, 0.89011714, 1.00000000, + 0.82353066, 0.88727974, 1.00000000, + 0.81935641, 0.88452017, 1.00000000, + 0.81530175, 0.88183541, 1.00000000, + 0.81136180, 0.87922257, 1.00000000, + 0.80753191, 0.87667891, 1.00000000, + 0.80380769, 0.87420182, 1.00000000, + 0.80018497, 0.87178882, 1.00000000, + 0.79665980, 0.86943756, 1.00000000, + 0.79322843, 0.86714579, 1.00000000, + 0.78988728, 0.86491137, 1.00000000, + 0.78663296, 0.86273225, 1.00000000, + 0.78346225, 0.86060650, 1.00000000, + 0.78037207, 0.85853224, 1.00000000, + 0.77735950, 0.85650771, 1.00000000, + 0.77442176, 0.85453121, 1.00000000, + 0.77155617, 0.85260112, 1.00000000, + 0.76876022, 0.85071588, 1.00000000, + 0.76603147, 0.84887402, 1.00000000, + 0.76336762, 0.84707411, 1.00000000, + 0.76076645, 0.84531479, 1.00000000, + 0.75822586, 0.84359476, 1.00000000, + 0.75574383, 0.84191277, 1.00000000, + 0.75331843, 0.84026762, 1.00000000, + 0.75094780, 0.83865816, 1.00000000, + 0.74863017, 0.83708329, 1.00000000, + 0.74636386, 0.83554194, 1.00000000, + 0.74414722, 0.83403311, 1.00000000, + 0.74197871, 0.83255582, 1.00000000, + 0.73985682, 0.83110912, 1.00000000, + 0.73778012, 0.82969211, 1.00000000, + 0.73574723, 0.82830393, 1.00000000, + 0.73375683, 0.82694373, 1.00000000, + 0.73180765, 0.82561071, 1.00000000, + 0.72989845, 0.82430410, 1.00000000, + 0.72802807, 0.82302316, 1.00000000, + 0.72619537, 0.82176715, 1.00000000, + 0.72439927, 0.82053539, 1.00000000, + 0.72263872, 0.81932722, 1.00000000, + 0.72091270, 0.81814197, 1.00000000, + 0.71922025, 0.81697905, 1.00000000, + 0.71756043, 0.81583783, 1.00000000, + 0.71593234, 0.81471775, 1.00000000, + 0.71433510, 0.81361825, 1.00000000, + 0.71276788, 0.81253878, 1.00000000, + 0.71122987, 0.81147883, 1.00000000, + 0.70972029, 0.81043789, 1.00000000, + 0.70823838, 0.80941546, 1.00000000, + 0.70678342, 0.80841109, 1.00000000, + 0.70535469, 0.80742432, 1.00000000, + 0.70395153, 0.80645469, 1.00000000, + 0.70257327, 0.80550180, 1.00000000, + 0.70121928, 0.80456522, 1.00000000, + 0.69988894, 0.80364455, 1.00000000, + 0.69858167, 0.80273941, 1.00000000, + 0.69729688, 0.80184943, 1.00000000, + 0.69603402, 0.80097423, 1.00000000, + 0.69479255, 0.80011347, 1.00000000, + 0.69357196, 0.79926681, 1.00000000, + 0.69237173, 0.79843391, 1.00000000, + 0.69119138, 0.79761446, 1.00000000, + 0.69003044, 0.79680814, 1.00000000, + 0.68888844, 0.79601466, 1.00000000, + 0.68776494, 0.79523371, 1.00000000, + 0.68665951, 0.79446502, 1.00000000, + 0.68557173, 0.79370830, 1.00000000, + 0.68450119, 0.79296330, 1.00000000, + 0.68344751, 0.79222975, 1.00000000, + 0.68241029, 0.79150740, 1.00000000, + 0.68138918, 0.79079600, 1.00000000, + 0.68038380, 0.79009531, 1.00000000, + 0.67939381, 0.78940511, 1.00000000, + 0.67841888, 0.78872517, 1.00000000, + 0.67745866, 0.78805526, 1.00000000, + 0.67651284, 0.78739518, 1.00000000, + 0.67558112, 0.78674472, 1.00000000, + 0.67466317, 0.78610368, 1.00000000, + 0.67375872, 0.78547186, 1.00000000, + 0.67286748, 0.78484907, 1.00000000, + 0.67198916, 0.78423512, 1.00000000, + 0.67112350, 0.78362984, 1.00000000, + 0.67027024, 0.78303305, 1.00000000, + 0.66942911, 0.78244457, 1.00000000, + 0.66859988, 0.78186425, 1.00000000, + 0.66778228, 0.78129191, 1.00000000, + 0.66697610, 0.78072740, 1.00000000, + 0.66618110, 0.78017057, 1.00000000, + 0.66539706, 0.77962127, 1.00000000, + 0.66462376, 0.77907934, 1.00000000, + 0.66386098, 0.77854465, 1.00000000, + 0.66310852, 0.77801705, 1.00000000, + 0.66236618, 0.77749642, 1.00000000, + 0.66163375, 0.77698261, 1.00000000, + 0.66091106, 0.77647551, 1.00000000, + 0.66019791, 0.77597498, 1.00000000, + 0.65949412, 0.77548090, 1.00000000, + 0.65879952, 0.77499315, 1.00000000, + 0.65811392, 0.77451161, 1.00000000, + 0.65743716, 0.77403618, 1.00000000, + 0.65676908, 0.77356673, 1.00000000, + 0.65610952, 0.77310316, 1.00000000, + 0.65545831, 0.77264537, 1.00000000, + 0.65481530, 0.77219324, 1.00000000, + 0.65418036, 0.77174669, 1.00000000, + 0.65355332, 0.77130560, 1.00000000, + 0.65293404, 0.77086988, 1.00000000, + 0.65232240, 0.77043944, 1.00000000, + 0.65171824, 0.77001419, 1.00000000, + 0.65112144, 0.76959404, 1.00000000, + 0.65053187, 0.76917889, 1.00000000, + 0.64994941, 0.76876866, 1.00000000, + 0.64937392, 0.76836326, 1.00000000 + }; + +} diff --git a/core/java/com/android/internal/util/custom/Concierge.java b/core/java/com/android/internal/util/custom/Concierge.java new file mode 100644 index 000000000000..74d8d73bf535 --- /dev/null +++ b/core/java/com/android/internal/util/custom/Concierge.java @@ -0,0 +1,151 @@ +/* + * 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 com.android.internal.util.custom; + +import android.os.Parcel; + +/** + * Simply, Concierge handles your parcels and makes sure they get marshalled and unmarshalled + * correctly when cross IPC boundaries even when there is a version mismatch between the client + * sdk level and the framework implementation. + * + *

On incoming parcel (to be unmarshalled): + * + *

+ *     ParcelInfo incomingParcelInfo = Concierge.receiveParcel(incomingParcel);
+ *     int parcelableVersion = incomingParcelInfo.getParcelVersion();
+ *
+ *     // Do unmarshalling steps here iterating over every plausible version
+ *
+ *     // Complete the process
+ *     incomingParcelInfo.complete();
+ * 
+ * + *

On outgoing parcel (to be marshalled): + * + *

+ *     ParcelInfo outgoingParcelInfo = Concierge.prepareParcel(incomingParcel);
+ *
+ *     // Do marshalling steps here iterating over every plausible version
+ *
+ *     // Complete the process
+ *     outgoingParcelInfo.complete();
+ * 
+ */ +public final class Concierge { + + /** Not instantiable */ + private Concierge() { + // Don't instantiate + } + + /** + * Since there might be a case where new versions of the lineage framework use applications running + * old versions of the protocol (and thus old versions of this class), we need a versioning + * system for the parcels sent between the core framework and its sdk users. + * + * This parcelable version should be the latest version API version listed in + * {@link LINEAGE_VERSION_CODES} + * @hide + */ + public static final int PARCELABLE_VERSION = 9; + + /** + * Tell the concierge to receive our parcel, so we can get information from it. + * + * MUST CALL {@link ParcelInfo#complete()} AFTER UNMARSHALLING. + * + * @param parcel Incoming parcel to be unmarshalled + * @return {@link ParcelInfo} containing parcel information, specifically the version. + */ + public static ParcelInfo receiveParcel(Parcel parcel) { + return new ParcelInfo(parcel); + } + + /** + * Prepare a parcel for the Concierge. + * + * MUST CALL {@link ParcelInfo#complete()} AFTER MARSHALLING. + * + * @param parcel Outgoing parcel to be marshalled + * @return {@link ParcelInfo} containing parcel information, specifically the version. + */ + public static ParcelInfo prepareParcel(Parcel parcel) { + return new ParcelInfo(parcel, PARCELABLE_VERSION); + } + + /** + * Parcel header info specific to the Parcel object that is passed in via + * {@link #prepareParcel(Parcel)} or {@link #receiveParcel(Parcel)}. The exposed method + * of {@link #getParcelVersion()} gets the api level of the parcel object. + */ + public final static class ParcelInfo { + private Parcel mParcel; + private int mParcelableVersion; + private int mParcelableSize; + private int mStartPosition; + private int mSizePosition; + private boolean mCreation = false; + + ParcelInfo(Parcel parcel) { + mCreation = false; + mParcel = parcel; + mParcelableVersion = parcel.readInt(); + mParcelableSize = parcel.readInt(); + mStartPosition = parcel.dataPosition(); + } + + ParcelInfo(Parcel parcel, int parcelableVersion) { + mCreation = true; + mParcel = parcel; + mParcelableVersion = parcelableVersion; + + // Write parcelable version, make sure to define explicit changes + // within {@link #PARCELABLE_VERSION); + mParcel.writeInt(mParcelableVersion); + + // Inject a placeholder that will store the parcel size from this point on + // (not including the size itself). + mSizePosition = parcel.dataPosition(); + mParcel.writeInt(0); + mStartPosition = parcel.dataPosition(); + } + + /** + * Get the parcel version from the {@link Parcel} received by the Concierge. + * @return {@link #PARCELABLE_VERSION} of the {@link Parcel} + */ + public int getParcelVersion() { + return mParcelableVersion; + } + + /** + * Complete the {@link ParcelInfo} for the Concierge. + */ + public void complete() { + if (mCreation) { + // Go back and write size + mParcelableSize = mParcel.dataPosition() - mStartPosition; + mParcel.setDataPosition(mSizePosition); + mParcel.writeInt(mParcelableSize); + mParcel.setDataPosition(mStartPosition + mParcelableSize); + } else { + mParcel.setDataPosition(mStartPosition + mParcelableSize); + } + } + } +} diff --git a/core/java/com/android/internal/util/custom/FileUtils.java b/core/java/com/android/internal/util/custom/FileUtils.java new file mode 100644 index 000000000000..6b7ba194008c --- /dev/null +++ b/core/java/com/android/internal/util/custom/FileUtils.java @@ -0,0 +1,162 @@ +/* + * 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 com.android.internal.util.custom; + +import android.util.Log; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.NullPointerException; +import java.lang.SecurityException; + +public final class FileUtils { + private static final String TAG = "FileUtils"; + + private FileUtils() { + // This class is not supposed to be instantiated + } + + /** + * Reads the first line of text from the given file. + * Reference {@link BufferedReader#readLine()} for clarification on what a line is + * + * @return the read line contents, or null on failure + */ + public static String readOneLine(String fileName) { + String line = null; + BufferedReader reader = null; + + try { + reader = new BufferedReader(new FileReader(fileName), 512); + line = reader.readLine(); + } catch (FileNotFoundException e) { + Log.w(TAG, "No such file " + fileName + " for reading", e); + } catch (IOException e) { + Log.e(TAG, "Could not read from file " + fileName, e); + } finally { + try { + if (reader != null) { + reader.close(); + } + } catch (IOException e) { + // Ignored, not much we can do anyway + } + } + + return line; + } + + /** + * Writes the given value into the given file + * + * @return true on success, false on failure + */ + public static boolean writeLine(String fileName, String value) { + BufferedWriter writer = null; + + try { + writer = new BufferedWriter(new FileWriter(fileName)); + writer.write(value); + } catch (FileNotFoundException e) { + Log.w(TAG, "No such file " + fileName + " for writing", e); + return false; + } catch (IOException e) { + Log.e(TAG, "Could not write to file " + fileName, e); + return false; + } finally { + try { + if (writer != null) { + writer.close(); + } + } catch (IOException e) { + // Ignored, not much we can do anyway + } + } + + return true; + } + + /** + * Checks whether the given file exists + * + * @return true if exists, false if not + */ + public static boolean fileExists(String fileName) { + final File file = new File(fileName); + return file.exists(); + } + + /** + * Checks whether the given file is readable + * + * @return true if readable, false if not + */ + public static boolean isFileReadable(String fileName) { + final File file = new File(fileName); + return file.exists() && file.canRead(); + } + + /** + * Checks whether the given file is writable + * + * @return true if writable, false if not + */ + public static boolean isFileWritable(String fileName) { + final File file = new File(fileName); + return file.exists() && file.canWrite(); + } + + /** + * Deletes an existing file + * + * @return true if the delete was successful, false if not + */ + public static boolean delete(String fileName) { + final File file = new File(fileName); + boolean ok = false; + try { + ok = file.delete(); + } catch (SecurityException e) { + Log.w(TAG, "SecurityException trying to delete " + fileName, e); + } + return ok; + } + + /** + * Renames an existing file + * + * @return true if the rename was successful, false if not + */ + public static boolean rename(String srcPath, String dstPath) { + final File srcFile = new File(srcPath); + final File dstFile = new File(dstPath); + boolean ok = false; + try { + ok = srcFile.renameTo(dstFile); + } catch (SecurityException e) { + Log.w(TAG, "SecurityException trying to rename " + srcPath + " to " + dstPath, e); + } catch (NullPointerException e) { + Log.e(TAG, "NullPointerException trying to rename " + srcPath + " to " + dstPath, e); + } + return ok; + } +} diff --git a/core/java/com/android/internal/util/custom/MathUtils.java b/core/java/com/android/internal/util/custom/MathUtils.java new file mode 100644 index 000000000000..ca95c8abe798 --- /dev/null +++ b/core/java/com/android/internal/util/custom/MathUtils.java @@ -0,0 +1,67 @@ +/* + * 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 com.android.internal.util.custom; + +public final class MathUtils { + + /** + * Given a range of values which change continuously in a non-linear way, + * we can map back and forth to a linear scale using some quadratic equations. + * + * The linear scale ranges from 0 -> 1. This method will calculate the + * coefficients needed to solve the conversion functions in the next two methods. + * + * lower = actual value when linear value = 0 + * mid = actual value when linear value = .5 + * upper actual value when linear value = 1 + * + * @param lower + * @param mid + * @param upper + * @return array of coefficients + */ + public static double[] powerCurve(double lower, double mid, double upper) { + final double[] curve = new double[3]; + curve[0] = ((lower * upper) - (mid * mid)) / (lower - (2 * mid) + upper); + curve[1] = Math.pow((mid - lower), 2) / (lower - (2 * mid) + upper); + curve[2] = 2 * Math.log((upper - mid) / (mid - lower)); + return curve; + } + + /** + * Map a value on a power curve to a linear value + * + * @param curve obtained from powerCurve() + * @param value to convert to linear scale + * @return linear value from 0 -> 1 + */ + public static double powerCurveToLinear(final double[] curve, double value) { + return Math.log((value - curve[0]) / curve[1]) / curve[2]; + } + + /** + * Map a value on a linear scale to a value on a power curve + * + * @param curve obtained from powerCurve() + * @param value from 0 -> 1 to map onto power curve + * @return actual value on the given curve + */ + public static double linearToPowerCurve(final double[] curve, double value) { + return curve[0] + curve[1] * Math.exp(curve[2] * value); + } + + +} diff --git a/core/java/com/android/internal/util/custom/palette/ColorCutQuantizer.java b/core/java/com/android/internal/util/custom/palette/ColorCutQuantizer.java new file mode 100644 index 000000000000..421049381d72 --- /dev/null +++ b/core/java/com/android/internal/util/custom/palette/ColorCutQuantizer.java @@ -0,0 +1,517 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util.custom.palette; + +import android.graphics.Color; +import android.util.TimingLogger; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.PriorityQueue; + +import com.android.internal.util.custom.palette.Palette.Swatch; + +/** + * An color quantizer based on the Median-cut algorithm, but optimized for picking out distinct + * colors rather than representation colors. + * + * The color space is represented as a 3-dimensional cube with each dimension being an RGB + * component. The cube is then repeatedly divided until we have reduced the color space to the + * requested number of colors. An average color is then generated from each cube. + * + * What makes this different to median-cut is that median-cut divided cubes so that all of the cubes + * have roughly the same population, where this quantizer divides boxes based on their color volume. + * This means that the color space is divided into distinct colors, rather than representative + * colors. + * + * @hide + */ +final class ColorCutQuantizer { + + private static final String LOG_TAG = "ColorCutQuantizer"; + private static final boolean LOG_TIMINGS = false; + + private static final int COMPONENT_RED = -3; + private static final int COMPONENT_GREEN = -2; + private static final int COMPONENT_BLUE = -1; + + private static final int QUANTIZE_WORD_WIDTH = 5; + private static final int QUANTIZE_WORD_MASK = (1 << QUANTIZE_WORD_WIDTH) - 1; + + final int[] mColors; + final int[] mHistogram; + final List mQuantizedColors; + final TimingLogger mTimingLogger; + final Palette.Filter[] mFilters; + + private final float[] mTempHsl = new float[3]; + + /** + * Constructor. + * + * @param pixels histogram representing an image's pixel data + * @param maxColors The maximum number of colors that should be in the result palette. + * @param filters Set of filters to use in the quantization stage + */ + ColorCutQuantizer(final int[] pixels, final int maxColors, final Palette.Filter[] filters) { + mTimingLogger = LOG_TIMINGS ? new TimingLogger(LOG_TAG, "Creation") : null; + mFilters = filters; + + final int[] hist = mHistogram = new int[1 << (QUANTIZE_WORD_WIDTH * 3)]; + for (int i = 0; i < pixels.length; i++) { + final int quantizedColor = quantizeFromRgb888(pixels[i]); + // Now update the pixel value to the quantized value + pixels[i] = quantizedColor; + // And update the histogram + hist[quantizedColor]++; + } + + if (LOG_TIMINGS) { + mTimingLogger.addSplit("Histogram created"); + } + + // Now let's count the number of distinct colors + int distinctColorCount = 0; + for (int color = 0; color < hist.length; color++) { + if (hist[color] > 0 && shouldIgnoreColor(color)) { + // If we should ignore the color, set the population to 0 + hist[color] = 0; + } + if (hist[color] > 0) { + // If the color has population, increase the distinct color count + distinctColorCount++; + } + } + + if (LOG_TIMINGS) { + mTimingLogger.addSplit("Filtered colors and distinct colors counted"); + } + + // Now lets go through create an array consisting of only distinct colors + final int[] colors = mColors = new int[distinctColorCount]; + int distinctColorIndex = 0; + for (int color = 0; color < hist.length; color++) { + if (hist[color] > 0) { + colors[distinctColorIndex++] = color; + } + } + + if (LOG_TIMINGS) { + mTimingLogger.addSplit("Distinct colors copied into array"); + } + + if (distinctColorCount <= maxColors) { + // The image has fewer colors than the maximum requested, so just return the colors + mQuantizedColors = new ArrayList<>(); + for (int color : colors) { + mQuantizedColors.add(new Swatch(approximateToRgb888(color), hist[color])); + } + + if (LOG_TIMINGS) { + mTimingLogger.addSplit("Too few colors present. Copied to Swatches"); + mTimingLogger.dumpToLog(); + } + } else { + // We need use quantization to reduce the number of colors + mQuantizedColors = quantizePixels(maxColors); + + if (LOG_TIMINGS) { + mTimingLogger.addSplit("Quantized colors computed"); + mTimingLogger.dumpToLog(); + } + } + } + + /** + * @return the list of quantized colors + */ + List getQuantizedColors() { + return mQuantizedColors; + } + + private List quantizePixels(int maxColors) { + // Create the priority queue which is sorted by volume descending. This means we always + // split the largest box in the queue + final PriorityQueue pq = new PriorityQueue<>(maxColors, VBOX_COMPARATOR_VOLUME); + + // To start, offer a box which contains all of the colors + pq.offer(new Vbox(0, mColors.length - 1)); + + // Now go through the boxes, splitting them until we have reached maxColors or there are no + // more boxes to split + splitBoxes(pq, maxColors); + + // Finally, return the average colors of the color boxes + return generateAverageColors(pq); + } + + /** + * Iterate through the {@link java.util.Queue}, popping + * {@link ColorCutQuantizer.Vbox} objects from the queue + * and splitting them. Once split, the new box and the remaining box are offered back to the + * queue. + * + * @param queue {@link java.util.PriorityQueue} to poll for boxes + * @param maxSize Maximum amount of boxes to split + */ + private void splitBoxes(final PriorityQueue queue, final int maxSize) { + while (queue.size() < maxSize) { + final Vbox vbox = queue.poll(); + + if (vbox != null && vbox.canSplit()) { + // First split the box, and offer the result + queue.offer(vbox.splitBox()); + + if (LOG_TIMINGS) { + mTimingLogger.addSplit("Box split"); + } + // Then offer the box back + queue.offer(vbox); + } else { + if (LOG_TIMINGS) { + mTimingLogger.addSplit("All boxes split"); + } + // If we get here then there are no more boxes to split, so return + return; + } + } + } + + private List generateAverageColors(Collection vboxes) { + ArrayList colors = new ArrayList<>(vboxes.size()); + for (Vbox vbox : vboxes) { + Swatch swatch = vbox.getAverageColor(); + if (!shouldIgnoreColor(swatch)) { + // As we're averaging a color box, we can still get colors which we do not want, so + // we check again here + colors.add(swatch); + } + } + return colors; + } + + /** + * Represents a tightly fitting box around a color space. + */ + private class Vbox { + // lower and upper index are inclusive + private int mLowerIndex; + private int mUpperIndex; + // Population of colors within this box + private int mPopulation; + + private int mMinRed, mMaxRed; + private int mMinGreen, mMaxGreen; + private int mMinBlue, mMaxBlue; + + Vbox(int lowerIndex, int upperIndex) { + mLowerIndex = lowerIndex; + mUpperIndex = upperIndex; + fitBox(); + } + + final int getVolume() { + return (mMaxRed - mMinRed + 1) * (mMaxGreen - mMinGreen + 1) * + (mMaxBlue - mMinBlue + 1); + } + + final boolean canSplit() { + return getColorCount() > 1; + } + + final int getColorCount() { + return 1 + mUpperIndex - mLowerIndex; + } + + /** + * Recomputes the boundaries of this box to tightly fit the colors within the box. + */ + final void fitBox() { + final int[] colors = mColors; + final int[] hist = mHistogram; + + // Reset the min and max to opposite values + int minRed, minGreen, minBlue; + minRed = minGreen = minBlue = Integer.MAX_VALUE; + int maxRed, maxGreen, maxBlue; + maxRed = maxGreen = maxBlue = Integer.MIN_VALUE; + int count = 0; + + for (int i = mLowerIndex; i <= mUpperIndex; i++) { + final int color = colors[i]; + count += hist[color]; + + final int r = quantizedRed(color); + final int g = quantizedGreen(color); + final int b = quantizedBlue(color); + if (r > maxRed) { + maxRed = r; + } + if (r < minRed) { + minRed = r; + } + if (g > maxGreen) { + maxGreen = g; + } + if (g < minGreen) { + minGreen = g; + } + if (b > maxBlue) { + maxBlue = b; + } + if (b < minBlue) { + minBlue = b; + } + } + + mMinRed = minRed; + mMaxRed = maxRed; + mMinGreen = minGreen; + mMaxGreen = maxGreen; + mMinBlue = minBlue; + mMaxBlue = maxBlue; + mPopulation = count; + } + + /** + * Split this color box at the mid-point along it's longest dimension + * + * @return the new ColorBox + */ + final Vbox splitBox() { + if (!canSplit()) { + throw new IllegalStateException("Can not split a box with only 1 color"); + } + + // find median along the longest dimension + final int splitPoint = findSplitPoint(); + + Vbox newBox = new Vbox(splitPoint + 1, mUpperIndex); + + // Now change this box's upperIndex and recompute the color boundaries + mUpperIndex = splitPoint; + fitBox(); + + return newBox; + } + + /** + * @return the dimension which this box is largest in + */ + final int getLongestColorDimension() { + final int redLength = mMaxRed - mMinRed; + final int greenLength = mMaxGreen - mMinGreen; + final int blueLength = mMaxBlue - mMinBlue; + + if (redLength >= greenLength && redLength >= blueLength) { + return COMPONENT_RED; + } else if (greenLength >= redLength && greenLength >= blueLength) { + return COMPONENT_GREEN; + } else { + return COMPONENT_BLUE; + } + } + + /** + * Finds the point within this box's lowerIndex and upperIndex index of where to split. + * + * This is calculated by finding the longest color dimension, and then sorting the + * sub-array based on that dimension value in each color. The colors are then iterated over + * until a color is found with at least the midpoint of the whole box's dimension midpoint. + * + * @return the index of the colors array to split from + */ + final int findSplitPoint() { + final int longestDimension = getLongestColorDimension(); + final int[] colors = mColors; + final int[] hist = mHistogram; + + // We need to sort the colors in this box based on the longest color dimension. + // As we can't use a Comparator to define the sort logic, we modify each color so that + // it's most significant is the desired dimension + modifySignificantOctet(colors, longestDimension, mLowerIndex, mUpperIndex); + + // Now sort... Arrays.sort uses a exclusive toIndex so we need to add 1 + Arrays.sort(colors, mLowerIndex, mUpperIndex + 1); + + // Now revert all of the colors so that they are packed as RGB again + modifySignificantOctet(colors, longestDimension, mLowerIndex, mUpperIndex); + + final int midPoint = mPopulation / 2; + for (int i = mLowerIndex, count = 0; i <= mUpperIndex; i++) { + count += hist[colors[i]]; + if (count >= midPoint) { + return i; + } + } + + return mLowerIndex; + } + + /** + * @return the average color of this box. + */ + final Swatch getAverageColor() { + final int[] colors = mColors; + final int[] hist = mHistogram; + int redSum = 0; + int greenSum = 0; + int blueSum = 0; + int totalPopulation = 0; + + for (int i = mLowerIndex; i <= mUpperIndex; i++) { + final int color = colors[i]; + final int colorPopulation = hist[color]; + + totalPopulation += colorPopulation; + redSum += colorPopulation * quantizedRed(color); + greenSum += colorPopulation * quantizedGreen(color); + blueSum += colorPopulation * quantizedBlue(color); + } + + final int redMean = Math.round(redSum / (float) totalPopulation); + final int greenMean = Math.round(greenSum / (float) totalPopulation); + final int blueMean = Math.round(blueSum / (float) totalPopulation); + + return new Swatch(approximateToRgb888(redMean, greenMean, blueMean), totalPopulation); + } + } + + /** + * Modify the significant octet in a packed color int. Allows sorting based on the value of a + * single color component. This relies on all components being the same word size. + * + * @see Vbox#findSplitPoint() + */ + private static void modifySignificantOctet(final int[] a, final int dimension, + final int lower, final int upper) { + switch (dimension) { + case COMPONENT_RED: + // Already in RGB, no need to do anything + break; + case COMPONENT_GREEN: + // We need to do a RGB to GRB swap, or vice-versa + for (int i = lower; i <= upper; i++) { + final int color = a[i]; + a[i] = quantizedGreen(color) << (QUANTIZE_WORD_WIDTH + QUANTIZE_WORD_WIDTH) + | quantizedRed(color) << QUANTIZE_WORD_WIDTH + | quantizedBlue(color); + } + break; + case COMPONENT_BLUE: + // We need to do a RGB to BGR swap, or vice-versa + for (int i = lower; i <= upper; i++) { + final int color = a[i]; + a[i] = quantizedBlue(color) << (QUANTIZE_WORD_WIDTH + QUANTIZE_WORD_WIDTH) + | quantizedGreen(color) << QUANTIZE_WORD_WIDTH + | quantizedRed(color); + } + break; + } + } + + private boolean shouldIgnoreColor(int color565) { + final int rgb = approximateToRgb888(color565); + ColorUtils.colorToHSL(rgb, mTempHsl); + return shouldIgnoreColor(rgb, mTempHsl); + } + + private boolean shouldIgnoreColor(Swatch color) { + return shouldIgnoreColor(color.getRgb(), color.getHsl()); + } + + private boolean shouldIgnoreColor(int rgb, float[] hsl) { + if (mFilters != null && mFilters.length > 0) { + for (int i = 0, count = mFilters.length; i < count; i++) { + if (!mFilters[i].isAllowed(rgb, hsl)) { + return true; + } + } + } + return false; + } + + /** + * Comparator which sorts {@link Vbox} instances based on their volume, in descending order + */ + private static final Comparator VBOX_COMPARATOR_VOLUME = new Comparator() { + @Override + public int compare(Vbox lhs, Vbox rhs) { + return rhs.getVolume() - lhs.getVolume(); + } + }; + + /** + * Quantized a RGB888 value to have a word width of {@value #QUANTIZE_WORD_WIDTH}. + */ + private static int quantizeFromRgb888(int color) { + int r = modifyWordWidth(Color.red(color), 8, QUANTIZE_WORD_WIDTH); + int g = modifyWordWidth(Color.green(color), 8, QUANTIZE_WORD_WIDTH); + int b = modifyWordWidth(Color.blue(color), 8, QUANTIZE_WORD_WIDTH); + return r << (QUANTIZE_WORD_WIDTH + QUANTIZE_WORD_WIDTH) | g << QUANTIZE_WORD_WIDTH | b; + } + + /** + * Quantized RGB888 values to have a word width of {@value #QUANTIZE_WORD_WIDTH}. + */ + private static int approximateToRgb888(int r, int g, int b) { + return Color.rgb(modifyWordWidth(r, QUANTIZE_WORD_WIDTH, 8), + modifyWordWidth(g, QUANTIZE_WORD_WIDTH, 8), + modifyWordWidth(b, QUANTIZE_WORD_WIDTH, 8)); + } + + private static int approximateToRgb888(int color) { + return approximateToRgb888(quantizedRed(color), quantizedGreen(color), quantizedBlue(color)); + } + + /** + * @return red component of the quantized color + */ + private static int quantizedRed(int color) { + return (color >> (QUANTIZE_WORD_WIDTH + QUANTIZE_WORD_WIDTH)) & QUANTIZE_WORD_MASK; + } + + /** + * @return green component of a quantized color + */ + private static int quantizedGreen(int color) { + return (color >> QUANTIZE_WORD_WIDTH) & QUANTIZE_WORD_MASK; + } + + /** + * @return blue component of a quantized color + */ + private static int quantizedBlue(int color) { + return color & QUANTIZE_WORD_MASK; + } + + private static int modifyWordWidth(int value, int currentWidth, int targetWidth) { + final int newValue; + if (targetWidth > currentWidth) { + // If we're approximating up in word width, we'll shift up + newValue = value << (targetWidth - currentWidth); + } else { + // Else, we will just shift and keep the MSB + newValue = value >> (currentWidth - targetWidth); + } + return newValue & ((1 << targetWidth) - 1); + } + +} diff --git a/core/java/com/android/internal/util/custom/palette/ColorUtils.java b/core/java/com/android/internal/util/custom/palette/ColorUtils.java new file mode 100644 index 000000000000..4dbdfe7d3320 --- /dev/null +++ b/core/java/com/android/internal/util/custom/palette/ColorUtils.java @@ -0,0 +1,299 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util.custom.palette; + +import android.graphics.Color; + +/** + * A set of color-related utility methods, building upon those available in {@code Color}. + * + * @hide + */ +public class ColorUtils { + + private static final int MIN_ALPHA_SEARCH_MAX_ITERATIONS = 10; + private static final int MIN_ALPHA_SEARCH_PRECISION = 10; + + private ColorUtils() {} + + /** + * Composite two potentially translucent colors over each other and returns the result. + */ + public static int compositeColors(int foreground, int background) { + int bgAlpha = Color.alpha(background); + int fgAlpha = Color.alpha(foreground); + int a = compositeAlpha(fgAlpha, bgAlpha); + + int r = compositeComponent(Color.red(foreground), fgAlpha, + Color.red(background), bgAlpha, a); + int g = compositeComponent(Color.green(foreground), fgAlpha, + Color.green(background), bgAlpha, a); + int b = compositeComponent(Color.blue(foreground), fgAlpha, + Color.blue(background), bgAlpha, a); + + return Color.argb(a, r, g, b); + } + + private static int compositeAlpha(int foregroundAlpha, int backgroundAlpha) { + return 0xFF - (((0xFF - backgroundAlpha) * (0xFF - foregroundAlpha)) / 0xFF); + } + + private static int compositeComponent(int fgC, int fgA, int bgC, int bgA, int a) { + if (a == 0) return 0; + return ((0xFF * fgC * fgA) + (bgC * bgA * (0xFF - fgA))) / (a * 0xFF); + } + + /** + * Returns the luminance of a color. + * + * Formula defined here: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef + */ + public static double calculateLuminance(int color) { + double red = Color.red(color) / 255d; + red = red < 0.03928 ? red / 12.92 : Math.pow((red + 0.055) / 1.055, 2.4); + + double green = Color.green(color) / 255d; + green = green < 0.03928 ? green / 12.92 : Math.pow((green + 0.055) / 1.055, 2.4); + + double blue = Color.blue(color) / 255d; + blue = blue < 0.03928 ? blue / 12.92 : Math.pow((blue + 0.055) / 1.055, 2.4); + + return (0.2126 * red) + (0.7152 * green) + (0.0722 * blue); + } + + /** + * Returns the contrast ratio between {@code foreground} and {@code background}. + * {@code background} must be opaque. + *

+ * Formula defined + * here. + */ + public static double calculateContrast(int foreground, int background) { + if (Color.alpha(background) != 255) { + throw new IllegalArgumentException("background can not be translucent"); + } + if (Color.alpha(foreground) < 255) { + // If the foreground is translucent, composite the foreground over the background + foreground = compositeColors(foreground, background); + } + + final double luminance1 = calculateLuminance(foreground) + 0.05; + final double luminance2 = calculateLuminance(background) + 0.05; + + // Now return the lighter luminance divided by the darker luminance + return Math.max(luminance1, luminance2) / Math.min(luminance1, luminance2); + } + + /** + * Calculates the minimum alpha value which can be applied to {@code foreground} so that would + * have a contrast value of at least {@code minContrastRatio} when compared to + * {@code background}. + * + * @param foreground the foreground color. + * @param background the background color. Should be opaque. + * @param minContrastRatio the minimum contrast ratio. + * @return the alpha value in the range 0-255, or -1 if no value could be calculated. + */ + public static int calculateMinimumAlpha(int foreground, int background, + float minContrastRatio) { + if (Color.alpha(background) != 255) { + throw new IllegalArgumentException("background can not be translucent"); + } + + // First lets check that a fully opaque foreground has sufficient contrast + int testForeground = setAlphaComponent(foreground, 255); + double testRatio = calculateContrast(testForeground, background); + if (testRatio < minContrastRatio) { + // Fully opaque foreground does not have sufficient contrast, return error + return -1; + } + + // Binary search to find a value with the minimum value which provides sufficient contrast + int numIterations = 0; + int minAlpha = 0; + int maxAlpha = 255; + + while (numIterations <= MIN_ALPHA_SEARCH_MAX_ITERATIONS && + (maxAlpha - minAlpha) > MIN_ALPHA_SEARCH_PRECISION) { + final int testAlpha = (minAlpha + maxAlpha) / 2; + + testForeground = setAlphaComponent(foreground, testAlpha); + testRatio = calculateContrast(testForeground, background); + + if (testRatio < minContrastRatio) { + minAlpha = testAlpha; + } else { + maxAlpha = testAlpha; + } + + numIterations++; + } + + // Conservatively return the max of the range of possible alphas, which is known to pass. + return maxAlpha; + } + + /** + * Convert RGB components to HSL (hue-saturation-lightness). + *

    + *
  • hsl[0] is Hue [0 .. 360)
  • + *
  • hsl[1] is Saturation [0...1]
  • + *
  • hsl[2] is Lightness [0...1]
  • + *
+ * + * @param r red component value [0..255] + * @param g green component value [0..255] + * @param b blue component value [0..255] + * @param hsl 3 element array which holds the resulting HSL components. + */ + public static void RGBToHSL(int r, int g, int b, float[] hsl) { + final float rf = r / 255f; + final float gf = g / 255f; + final float bf = b / 255f; + + final float max = Math.max(rf, Math.max(gf, bf)); + final float min = Math.min(rf, Math.min(gf, bf)); + final float deltaMaxMin = max - min; + + float h, s; + float l = (max + min) / 2f; + + if (max == min) { + // Monochromatic + h = s = 0f; + } else { + if (max == rf) { + h = ((gf - bf) / deltaMaxMin) % 6f; + } else if (max == gf) { + h = ((bf - rf) / deltaMaxMin) + 2f; + } else { + h = ((rf - gf) / deltaMaxMin) + 4f; + } + + s = deltaMaxMin / (1f - Math.abs(2f * l - 1f)); + } + + h = (h * 60f) % 360f; + if (h < 0) { + h += 360f; + } + + hsl[0] = constrain(h, 0f, 360f); + hsl[1] = constrain(s, 0f, 1f); + hsl[2] = constrain(l, 0f, 1f); + } + + /** + * Convert the ARGB color to its HSL (hue-saturation-lightness) components. + *
    + *
  • hsl[0] is Hue [0 .. 360)
  • + *
  • hsl[1] is Saturation [0...1]
  • + *
  • hsl[2] is Lightness [0...1]
  • + *
+ * + * @param color the ARGB color to convert. The alpha component is ignored. + * @param hsl 3 element array which holds the resulting HSL components. + */ + public static void colorToHSL(int color, float[] hsl) { + RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), hsl); + } + + /** + * Convert HSL (hue-saturation-lightness) components to a RGB color. + *
    + *
  • hsl[0] is Hue [0 .. 360)
  • + *
  • hsl[1] is Saturation [0...1]
  • + *
  • hsl[2] is Lightness [0...1]
  • + *
+ * If hsv values are out of range, they are pinned. + * + * @param hsl 3 element array which holds the input HSL components. + * @return the resulting RGB color + */ + public static int HSLToColor(float[] hsl) { + final float h = hsl[0]; + final float s = hsl[1]; + final float l = hsl[2]; + + final float c = (1f - Math.abs(2 * l - 1f)) * s; + final float m = l - 0.5f * c; + final float x = c * (1f - Math.abs((h / 60f % 2f) - 1f)); + + final int hueSegment = (int) h / 60; + + int r = 0, g = 0, b = 0; + + switch (hueSegment) { + case 0: + r = Math.round(255 * (c + m)); + g = Math.round(255 * (x + m)); + b = Math.round(255 * m); + break; + case 1: + r = Math.round(255 * (x + m)); + g = Math.round(255 * (c + m)); + b = Math.round(255 * m); + break; + case 2: + r = Math.round(255 * m); + g = Math.round(255 * (c + m)); + b = Math.round(255 * (x + m)); + break; + case 3: + r = Math.round(255 * m); + g = Math.round(255 * (x + m)); + b = Math.round(255 * (c + m)); + break; + case 4: + r = Math.round(255 * (x + m)); + g = Math.round(255 * m); + b = Math.round(255 * (c + m)); + break; + case 5: + case 6: + r = Math.round(255 * (c + m)); + g = Math.round(255 * m); + b = Math.round(255 * (x + m)); + break; + } + + r = constrain(r, 0, 255); + g = constrain(g, 0, 255); + b = constrain(b, 0, 255); + + return Color.rgb(r, g, b); + } + + /** + * Set the alpha component of {@code color} to be {@code alpha}. + */ + public static int setAlphaComponent(int color, int alpha) { + if (alpha < 0 || alpha > 255) { + throw new IllegalArgumentException("alpha must be between 0 and 255."); + } + return (color & 0x00ffffff) | (alpha << 24); + } + + private static float constrain(float amount, float low, float high) { + return amount < low ? low : (amount > high ? high : amount); + } + + private static int constrain(int amount, int low, int high) { + return amount < low ? low : (amount > high ? high : amount); + } + +} diff --git a/core/java/com/android/internal/util/custom/palette/DefaultGenerator.java b/core/java/com/android/internal/util/custom/palette/DefaultGenerator.java new file mode 100644 index 000000000000..445d615b1547 --- /dev/null +++ b/core/java/com/android/internal/util/custom/palette/DefaultGenerator.java @@ -0,0 +1,244 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util.custom.palette; + +import java.util.List; + +import com.android.internal.util.custom.palette.Palette.Swatch; + +/** + * @hide + */ +class DefaultGenerator extends Palette.Generator { + + private static final float TARGET_DARK_LUMA = 0.26f; + private static final float MAX_DARK_LUMA = 0.45f; + + private static final float MIN_LIGHT_LUMA = 0.55f; + private static final float TARGET_LIGHT_LUMA = 0.74f; + + private static final float MIN_NORMAL_LUMA = 0.3f; + private static final float TARGET_NORMAL_LUMA = 0.5f; + private static final float MAX_NORMAL_LUMA = 0.7f; + + private static final float TARGET_MUTED_SATURATION = 0.3f; + private static final float MAX_MUTED_SATURATION = 0.4f; + + private static final float TARGET_VIBRANT_SATURATION = 1f; + private static final float MIN_VIBRANT_SATURATION = 0.35f; + + private static final float WEIGHT_SATURATION = 3f; + private static final float WEIGHT_LUMA = 6f; + private static final float WEIGHT_POPULATION = 1f; + + private List mSwatches; + + private int mHighestPopulation; + + private Swatch mVibrantSwatch; + private Swatch mMutedSwatch; + private Swatch mDarkVibrantSwatch; + private Swatch mDarkMutedSwatch; + private Swatch mLightVibrantSwatch; + private Swatch mLightMutedSwatch; + + @Override + public void generate(final List swatches) { + mSwatches = swatches; + + mHighestPopulation = findMaxPopulation(); + + generateVariationColors(); + + // Now try and generate any missing colors + generateEmptySwatches(); + } + + @Override + public Swatch getVibrantSwatch() { + return mVibrantSwatch; + } + + @Override + public Swatch getLightVibrantSwatch() { + return mLightVibrantSwatch; + } + + @Override + public Swatch getDarkVibrantSwatch() { + return mDarkVibrantSwatch; + } + + @Override + public Swatch getMutedSwatch() { + return mMutedSwatch; + } + + @Override + public Swatch getLightMutedSwatch() { + return mLightMutedSwatch; + } + + @Override + public Swatch getDarkMutedSwatch() { + return mDarkMutedSwatch; + } + + private void generateVariationColors() { + mVibrantSwatch = findColorVariation(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA, + TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f); + + mLightVibrantSwatch = findColorVariation(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1f, + TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f); + + mDarkVibrantSwatch = findColorVariation(TARGET_DARK_LUMA, 0f, MAX_DARK_LUMA, + TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f); + + mMutedSwatch = findColorVariation(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA, + TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION); + + mLightMutedSwatch = findColorVariation(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1f, + TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION); + + mDarkMutedSwatch = findColorVariation(TARGET_DARK_LUMA, 0f, MAX_DARK_LUMA, + TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION); + } + + /** + * Try and generate any missing swatches from the swatches we did find. + */ + private void generateEmptySwatches() { + if (mVibrantSwatch == null) { + // If we do not have a vibrant color... + if (mDarkVibrantSwatch != null) { + // ...but we do have a dark vibrant, generate the value by modifying the luma + final float[] newHsl = copyHslValues(mDarkVibrantSwatch); + newHsl[2] = TARGET_NORMAL_LUMA; + mVibrantSwatch = new Swatch(ColorUtils.HSLToColor(newHsl), 0); + } + } + + if (mDarkVibrantSwatch == null) { + // If we do not have a dark vibrant color... + if (mVibrantSwatch != null) { + // ...but we do have a vibrant, generate the value by modifying the luma + final float[] newHsl = copyHslValues(mVibrantSwatch); + newHsl[2] = TARGET_DARK_LUMA; + mDarkVibrantSwatch = new Swatch(ColorUtils.HSLToColor(newHsl), 0); + } + } + } + + /** + * Find the {@link Palette.Swatch} with the highest population value and return the population. + */ + private int findMaxPopulation() { + int population = 0; + for (Swatch swatch : mSwatches) { + population = Math.max(population, swatch.getPopulation()); + } + return population; + } + + private Swatch findColorVariation(float targetLuma, float minLuma, float maxLuma, + float targetSaturation, float minSaturation, float maxSaturation) { + Swatch max = null; + float maxValue = 0f; + + for (Swatch swatch : mSwatches) { + final float sat = swatch.getHsl()[1]; + final float luma = swatch.getHsl()[2]; + + if (sat >= minSaturation && sat <= maxSaturation && + luma >= minLuma && luma <= maxLuma && + !isAlreadySelected(swatch)) { + float value = createComparisonValue(sat, targetSaturation, luma, targetLuma, + swatch.getPopulation(), mHighestPopulation); + if (max == null || value > maxValue) { + max = swatch; + maxValue = value; + } + } + } + + return max; + } + + /** + * @return true if we have already selected {@code swatch} + */ + private boolean isAlreadySelected(Swatch swatch) { + return mVibrantSwatch == swatch || mDarkVibrantSwatch == swatch || + mLightVibrantSwatch == swatch || mMutedSwatch == swatch || + mDarkMutedSwatch == swatch || mLightMutedSwatch == swatch; + } + + private static float createComparisonValue(float saturation, float targetSaturation, + float luma, float targetLuma, + int population, int maxPopulation) { + return createComparisonValue(saturation, targetSaturation, WEIGHT_SATURATION, + luma, targetLuma, WEIGHT_LUMA, + population, maxPopulation, WEIGHT_POPULATION); + } + + private static float createComparisonValue( + float saturation, float targetSaturation, float saturationWeight, + float luma, float targetLuma, float lumaWeight, + int population, int maxPopulation, float populationWeight) { + return weightedMean( + invertDiff(saturation, targetSaturation), saturationWeight, + invertDiff(luma, targetLuma), lumaWeight, + population / (float) maxPopulation, populationWeight + ); + } + + /** + * Copy a {@link Swatch}'s HSL values into a new float[]. + */ + private static float[] copyHslValues(Swatch color) { + final float[] newHsl = new float[3]; + System.arraycopy(color.getHsl(), 0, newHsl, 0, 3); + return newHsl; + } + + /** + * Returns a value in the range 0-1. 1 is returned when {@code value} equals the + * {@code targetValue} and then decreases as the absolute difference between {@code value} and + * {@code targetValue} increases. + * + * @param value the item's value + * @param targetValue the value which we desire + */ + private static float invertDiff(float value, float targetValue) { + return 1f - Math.abs(value - targetValue); + } + + private static float weightedMean(float... values) { + float sum = 0f; + float sumWeight = 0f; + + for (int i = 0; i < values.length; i += 2) { + float value = values[i]; + float weight = values[i + 1]; + + sum += (value * weight); + sumWeight += weight; + } + + return sum / sumWeight; + } +} diff --git a/core/java/com/android/internal/util/custom/palette/Palette.java b/core/java/com/android/internal/util/custom/palette/Palette.java new file mode 100644 index 000000000000..54b7d0de974b --- /dev/null +++ b/core/java/com/android/internal/util/custom/palette/Palette.java @@ -0,0 +1,740 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util.custom.palette; + +import android.graphics.Bitmap; +import android.graphics.Color; +import android.os.AsyncTask; +import android.annotation.ColorInt; +import android.annotation.Nullable; +import android.util.TimingLogger; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * A helper class to extract prominent colors from an image. + *

+ * A number of colors with different profiles are extracted from the image: + *

    + *
  • Vibrant
  • + *
  • Vibrant Dark
  • + *
  • Vibrant Light
  • + *
  • Muted
  • + *
  • Muted Dark
  • + *
  • Muted Light
  • + *
+ * These can be retrieved from the appropriate getter method. + * + *

+ * Instances are created with a {@link Builder} which supports several options to tweak the + * generated Palette. See that class' documentation for more information. + *

+ * Generation should always be completed on a background thread, ideally the one in + * which you load your image on. {@link Builder} supports both synchronous and asynchronous + * generation: + * + *

+ * // Synchronous
+ * Palette p = Palette.from(bitmap).generate();
+ *
+ * // Asynchronous
+ * Palette.from(bitmap).generate(new PaletteAsyncListener() {
+ *     public void onGenerated(Palette p) {
+ *         // Use generated instance
+ *     }
+ * });
+ * 
+ * + * @hide + */ +public final class Palette { + + /** + * Listener to be used with {@link #generateAsync(Bitmap, PaletteAsyncListener)} or + * {@link #generateAsync(Bitmap, int, PaletteAsyncListener)} + */ + public interface PaletteAsyncListener { + + /** + * Called when the {@link Palette} has been generated. + */ + void onGenerated(Palette palette); + } + + private static final int DEFAULT_RESIZE_BITMAP_MAX_DIMENSION = 192; + private static final int DEFAULT_CALCULATE_NUMBER_COLORS = 16; + + private static final float MIN_CONTRAST_TITLE_TEXT = 3.0f; + private static final float MIN_CONTRAST_BODY_TEXT = 4.5f; + + private static final String LOG_TAG = "Palette"; + private static final boolean LOG_TIMINGS = false; + + /** + * Start generating a {@link Palette} with the returned {@link Builder} instance. + */ + public static Builder from(Bitmap bitmap) { + return new Builder(bitmap); + } + + /** + * Generate a {@link Palette} from the pre-generated list of {@link Palette.Swatch} swatches. + * This is useful for testing, or if you want to resurrect a {@link Palette} instance from a + * list of swatches. Will return null if the {@code swatches} is null. + */ + public static Palette from(List swatches) { + return new Builder(swatches).generate(); + } + + /** + * @deprecated Use {@link Builder} to generate the Palette. + */ + @Deprecated + public static Palette generate(Bitmap bitmap) { + return from(bitmap).generate(); + } + + /** + * @deprecated Use {@link Builder} to generate the Palette. + */ + @Deprecated + public static Palette generate(Bitmap bitmap, int numColors) { + return from(bitmap).maximumColorCount(numColors).generate(); + } + + /** + * @deprecated Use {@link Builder} to generate the Palette. + */ + @Deprecated + public static AsyncTask generateAsync( + Bitmap bitmap, PaletteAsyncListener listener) { + return from(bitmap).generate(listener); + } + + /** + * @deprecated Use {@link Builder} to generate the Palette. + */ + @Deprecated + public static AsyncTask generateAsync( + final Bitmap bitmap, final int numColors, final PaletteAsyncListener listener) { + return from(bitmap).maximumColorCount(numColors).generate(listener); + } + + private final List mSwatches; + private final Generator mGenerator; + + private Palette(List swatches, Generator generator) { + mSwatches = swatches; + mGenerator = generator; + } + + /** + * Returns all of the swatches which make up the palette. + */ + public List getSwatches() { + return Collections.unmodifiableList(mSwatches); + } + + /** + * Returns the most vibrant swatch in the palette. Might be null. + */ + @Nullable + public Swatch getVibrantSwatch() { + return mGenerator.getVibrantSwatch(); + } + + /** + * Returns a light and vibrant swatch from the palette. Might be null. + */ + @Nullable + public Swatch getLightVibrantSwatch() { + return mGenerator.getLightVibrantSwatch(); + } + + /** + * Returns a dark and vibrant swatch from the palette. Might be null. + */ + @Nullable + public Swatch getDarkVibrantSwatch() { + return mGenerator.getDarkVibrantSwatch(); + } + + /** + * Returns a muted swatch from the palette. Might be null. + */ + @Nullable + public Swatch getMutedSwatch() { + return mGenerator.getMutedSwatch(); + } + + /** + * Returns a muted and light swatch from the palette. Might be null. + */ + @Nullable + public Swatch getLightMutedSwatch() { + return mGenerator.getLightMutedSwatch(); + } + + /** + * Returns a muted and dark swatch from the palette. Might be null. + */ + @Nullable + public Swatch getDarkMutedSwatch() { + return mGenerator.getDarkMutedSwatch(); + } + + /** + * Returns the most vibrant color in the palette as an RGB packed int. + * + * @param defaultColor value to return if the swatch isn't available + */ + @ColorInt + public int getVibrantColor(@ColorInt int defaultColor) { + Swatch swatch = getVibrantSwatch(); + return swatch != null ? swatch.getRgb() : defaultColor; + } + + /** + * Returns a light and vibrant color from the palette as an RGB packed int. + * + * @param defaultColor value to return if the swatch isn't available + */ + @ColorInt + public int getLightVibrantColor(@ColorInt int defaultColor) { + Swatch swatch = getLightVibrantSwatch(); + return swatch != null ? swatch.getRgb() : defaultColor; + } + + /** + * Returns a dark and vibrant color from the palette as an RGB packed int. + * + * @param defaultColor value to return if the swatch isn't available + */ + @ColorInt + public int getDarkVibrantColor(@ColorInt int defaultColor) { + Swatch swatch = getDarkVibrantSwatch(); + return swatch != null ? swatch.getRgb() : defaultColor; + } + + /** + * Returns a muted color from the palette as an RGB packed int. + * + * @param defaultColor value to return if the swatch isn't available + */ + @ColorInt + public int getMutedColor(@ColorInt int defaultColor) { + Swatch swatch = getMutedSwatch(); + return swatch != null ? swatch.getRgb() : defaultColor; + } + + /** + * Returns a muted and light color from the palette as an RGB packed int. + * + * @param defaultColor value to return if the swatch isn't available + */ + @ColorInt + public int getLightMutedColor(@ColorInt int defaultColor) { + Swatch swatch = getLightMutedSwatch(); + return swatch != null ? swatch.getRgb() : defaultColor; + } + + /** + * Returns a muted and dark color from the palette as an RGB packed int. + * + * @param defaultColor value to return if the swatch isn't available + */ + @ColorInt + public int getDarkMutedColor(@ColorInt int defaultColor) { + Swatch swatch = getDarkMutedSwatch(); + return swatch != null ? swatch.getRgb() : defaultColor; + } + + /** + * Scale the bitmap down so that it's largest dimension is {@code targetMaxDimension}. + * If {@code bitmap} is smaller than this, then it is returned. + */ + private static Bitmap scaleBitmapDown(Bitmap bitmap, final int targetMaxDimension) { + final int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight()); + + if (maxDimension <= targetMaxDimension) { + // If the bitmap is small enough already, just return it + return bitmap; + } + + final float scaleRatio = targetMaxDimension / (float) maxDimension; + return Bitmap.createScaledBitmap(bitmap, + Math.round(bitmap.getWidth() * scaleRatio), + Math.round(bitmap.getHeight() * scaleRatio), + false); + } + + /** + * Represents a color swatch generated from an image's palette. The RGB color can be retrieved + * by calling {@link #getRgb()}. + */ + public static final class Swatch { + private final int mRed, mGreen, mBlue; + private final int mRgb; + private final int mPopulation; + + private boolean mGeneratedTextColors; + private int mTitleTextColor; + private int mBodyTextColor; + + private float[] mHsl; + + public Swatch(@ColorInt int color, int population) { + mRed = Color.red(color); + mGreen = Color.green(color); + mBlue = Color.blue(color); + mRgb = color; + mPopulation = population; + } + + Swatch(int red, int green, int blue, int population) { + mRed = red; + mGreen = green; + mBlue = blue; + mRgb = Color.rgb(red, green, blue); + mPopulation = population; + } + + /** + * @return this swatch's RGB color value + */ + @ColorInt + public int getRgb() { + return mRgb; + } + + /** + * Return this swatch's HSL values. + * hsv[0] is Hue [0 .. 360) + * hsv[1] is Saturation [0...1] + * hsv[2] is Lightness [0...1] + */ + public float[] getHsl() { + if (mHsl == null) { + mHsl = new float[3]; + ColorUtils.RGBToHSL(mRed, mGreen, mBlue, mHsl); + } + return mHsl; + } + + /** + * @return the number of pixels represented by this swatch + */ + public int getPopulation() { + return mPopulation; + } + + /** + * Returns an appropriate color to use for any 'title' text which is displayed over this + * {@link Swatch}'s color. This color is guaranteed to have sufficient contrast. + */ + @ColorInt + public int getTitleTextColor() { + ensureTextColorsGenerated(); + return mTitleTextColor; + } + + /** + * Returns an appropriate color to use for any 'body' text which is displayed over this + * {@link Swatch}'s color. This color is guaranteed to have sufficient contrast. + */ + @ColorInt + public int getBodyTextColor() { + ensureTextColorsGenerated(); + return mBodyTextColor; + } + + private void ensureTextColorsGenerated() { + if (!mGeneratedTextColors) { + // First check white, as most colors will be dark + final int lightBodyAlpha = ColorUtils.calculateMinimumAlpha( + Color.WHITE, mRgb, MIN_CONTRAST_BODY_TEXT); + final int lightTitleAlpha = ColorUtils.calculateMinimumAlpha( + Color.WHITE, mRgb, MIN_CONTRAST_TITLE_TEXT); + + if (lightBodyAlpha != -1 && lightTitleAlpha != -1) { + // If we found valid light values, use them and return + mBodyTextColor = ColorUtils.setAlphaComponent(Color.WHITE, lightBodyAlpha); + mTitleTextColor = ColorUtils.setAlphaComponent(Color.WHITE, lightTitleAlpha); + mGeneratedTextColors = true; + return; + } + + final int darkBodyAlpha = ColorUtils.calculateMinimumAlpha( + Color.BLACK, mRgb, MIN_CONTRAST_BODY_TEXT); + final int darkTitleAlpha = ColorUtils.calculateMinimumAlpha( + Color.BLACK, mRgb, MIN_CONTRAST_TITLE_TEXT); + + if (darkBodyAlpha != -1 && darkBodyAlpha != -1) { + // If we found valid dark values, use them and return + mBodyTextColor = ColorUtils.setAlphaComponent(Color.BLACK, darkBodyAlpha); + mTitleTextColor = ColorUtils.setAlphaComponent(Color.BLACK, darkTitleAlpha); + mGeneratedTextColors = true; + return; + } + + // If we reach here then we can not find title and body values which use the same + // lightness, we need to use mismatched values + mBodyTextColor = lightBodyAlpha != -1 + ? ColorUtils.setAlphaComponent(Color.WHITE, lightBodyAlpha) + : ColorUtils.setAlphaComponent(Color.BLACK, darkBodyAlpha); + mTitleTextColor = lightTitleAlpha != -1 + ? ColorUtils.setAlphaComponent(Color.WHITE, lightTitleAlpha) + : ColorUtils.setAlphaComponent(Color.BLACK, darkTitleAlpha); + mGeneratedTextColors = true; + } + } + + @Override + public String toString() { + return new StringBuilder(getClass().getSimpleName()) + .append(" [RGB: #").append(Integer.toHexString(getRgb())).append(']') + .append(" [HSL: ").append(Arrays.toString(getHsl())).append(']') + .append(" [Population: ").append(mPopulation).append(']') + .append(" [Title Text: #").append(Integer.toHexString(getTitleTextColor())) + .append(']') + .append(" [Body Text: #").append(Integer.toHexString(getBodyTextColor())) + .append(']').toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Swatch swatch = (Swatch) o; + return mPopulation == swatch.mPopulation && mRgb == swatch.mRgb; + } + + @Override + public int hashCode() { + return 31 * mRgb + mPopulation; + } + } + + /** + * Builder class for generating {@link Palette} instances. + */ + public static final class Builder { + private List mSwatches; + private Bitmap mBitmap; + private int mMaxColors = DEFAULT_CALCULATE_NUMBER_COLORS; + private int mResizeMaxDimension = DEFAULT_RESIZE_BITMAP_MAX_DIMENSION; + private final List mFilters = new ArrayList<>(); + + private Generator mGenerator; + + /** + * Construct a new {@link Builder} using a source {@link Bitmap} + */ + public Builder(Bitmap bitmap) { + this(); + if (bitmap == null || bitmap.isRecycled()) { + throw new IllegalArgumentException("Bitmap is not valid"); + } + mBitmap = bitmap; + } + + /** + * Construct a new {@link Builder} using a list of {@link Swatch} instances. + * Typically only used for testing. + */ + public Builder(List swatches) { + this(); + if (swatches == null || swatches.isEmpty()) { + throw new IllegalArgumentException("List of Swatches is not valid"); + } + mSwatches = swatches; + } + + private Builder() { + mFilters.add(DEFAULT_FILTER); + } + + /** + * Set the {@link Generator} to use when generating the {@link Palette}. If this is called + * with {@code null} then the default generator will be used. + */ + Builder generator(Generator generator) { + mGenerator = generator; + return this; + } + + /** + * Set the maximum number of colors to use in the quantization step when using a + * {@link android.graphics.Bitmap} as the source. + *

+ * Good values for depend on the source image type. For landscapes, good values are in + * the range 10-16. For images which are largely made up of people's faces then this + * value should be increased to ~24. + */ + public Builder maximumColorCount(int colors) { + mMaxColors = colors; + return this; + } + + /** + * Set the resize value when using a {@link android.graphics.Bitmap} as the source. + * If the bitmap's largest dimension is greater than the value specified, then the bitmap + * will be resized so that it's largest dimension matches {@code maxDimension}. If the + * bitmap is smaller or equal, the original is used as-is. + *

+ * This value has a large effect on the processing time. The larger the resized image is, + * the greater time it will take to generate the palette. The smaller the image is, the + * more detail is lost in the resulting image and thus less precision for color selection. + */ + public Builder resizeBitmapSize(int maxDimension) { + mResizeMaxDimension = maxDimension; + return this; + } + + /** + * Clear all added filters. This includes any default filters added automatically by + * {@link Palette}. + */ + public Builder clearFilters() { + mFilters.clear(); + return this; + } + + /** + * Add a filter to be able to have fine grained controlled over the colors which are + * allowed in the resulting palette. + * + * @param filter filter to add. + */ + public Builder addFilter(Filter filter) { + if (filter != null) { + mFilters.add(filter); + } + return this; + } + + /** + * Generate and return the {@link Palette} synchronously. + */ + public Palette generate() { + final TimingLogger logger = LOG_TIMINGS + ? new TimingLogger(LOG_TAG, "Generation") + : null; + + List swatches; + + if (mBitmap != null) { + // We have a Bitmap so we need to quantization to reduce the number of colors + + if (mResizeMaxDimension <= 0) { + throw new IllegalArgumentException( + "Minimum dimension size for resizing should should be >= 1"); + } + + // First we'll scale down the bitmap so it's largest dimension is as specified + final Bitmap scaledBitmap = scaleBitmapDown(mBitmap, mResizeMaxDimension); + + if (logger != null) { + logger.addSplit("Processed Bitmap"); + } + + // Now generate a quantizer from the Bitmap + final int width = scaledBitmap.getWidth(); + final int height = scaledBitmap.getHeight(); + final int[] pixels = new int[width * height]; + scaledBitmap.getPixels(pixels, 0, width, 0, 0, width, height); + + final ColorCutQuantizer quantizer = new ColorCutQuantizer(pixels, mMaxColors, + mFilters.isEmpty() ? null : mFilters.toArray(new Filter[0])); + + // If created a new bitmap, recycle it + if (scaledBitmap != mBitmap) { + scaledBitmap.recycle(); + } + swatches = quantizer.getQuantizedColors(); + + if (logger != null) { + logger.addSplit("Color quantization completed"); + } + } else { + // Else we're using the provided swatches + swatches = mSwatches; + } + + // If we haven't been provided with a generator, use the default + if (mGenerator == null) { + mGenerator = new DefaultGenerator(); + } + + // Now call let the Generator do it's thing + mGenerator.generate(swatches); + + if (logger != null) { + logger.addSplit("Generator.generate() completed"); + } + + // Now create a Palette instance + Palette p = new Palette(swatches, mGenerator); + + if (logger != null) { + logger.addSplit("Created Palette"); + logger.dumpToLog(); + } + + return p; + } + + /** + * Generate the {@link Palette} asynchronously. The provided listener's + * {@link PaletteAsyncListener#onGenerated} method will be called with the palette when + * generated. + */ + public AsyncTask generate(final PaletteAsyncListener listener) { + if (listener == null) { + throw new IllegalArgumentException("listener can not be null"); + } + + AsyncTask task = new AsyncTask() { + @Override + protected Palette doInBackground(Bitmap... params) { + return generate(); + } + + @Override + protected void onPostExecute(Palette colorExtractor) { + listener.onGenerated(colorExtractor); + } + }; + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mBitmap); + return task; + } + } + + static abstract class Generator { + + /** + * This method will be called with the {@link Palette.Swatch} that represent an image. + * You should process this list so that you have appropriate values when the other methods in + * class are called. + *

+ * This method will probably be called on a background thread. + */ + public abstract void generate(List swatches); + + /** + * Return the most vibrant {@link Palette.Swatch} + */ + public Palette.Swatch getVibrantSwatch() { + return null; + } + + /** + * Return a light and vibrant {@link Palette.Swatch} + */ + public Palette.Swatch getLightVibrantSwatch() { + return null; + } + + /** + * Return a dark and vibrant {@link Palette.Swatch} + */ + public Palette.Swatch getDarkVibrantSwatch() { + return null; + } + + /** + * Return a muted {@link Palette.Swatch} + */ + public Palette.Swatch getMutedSwatch() { + return null; + } + + /** + * Return a muted and light {@link Palette.Swatch} + */ + public Palette.Swatch getLightMutedSwatch() { + return null; + } + + /** + * Return a muted and dark {@link Palette.Swatch} + */ + public Palette.Swatch getDarkMutedSwatch() { + return null; + } + } + + /** + * A Filter provides a mechanism for exercising fine-grained control over which colors + * are valid within a resulting {@link Palette}. + */ + public interface Filter { + /** + * Hook to allow clients to be able filter colors from resulting palette. + * + * @param rgb the color in RGB888. + * @param hsl HSL representation of the color. + * + * @return true if the color is allowed, false if not. + * + * @see Builder#addFilter(Filter) + */ + boolean isAllowed(int rgb, float[] hsl); + } + + /** + * The default filter. + */ + private static final Filter DEFAULT_FILTER = new Filter() { + private static final float BLACK_MAX_LIGHTNESS = 0.05f; + private static final float WHITE_MIN_LIGHTNESS = 0.95f; + + @Override + public boolean isAllowed(int rgb, float[] hsl) { + return !isWhite(hsl) && !isBlack(hsl) && !isNearRedILine(hsl); + } + + /** + * @return true if the color represents a color which is close to black. + */ + private boolean isBlack(float[] hslColor) { + return hslColor[2] <= BLACK_MAX_LIGHTNESS; + } + + /** + * @return true if the color represents a color which is close to white. + */ + private boolean isWhite(float[] hslColor) { + return hslColor[2] >= WHITE_MIN_LIGHTNESS; + } + + /** + * @return true if the color lies close to the red side of the I line. + */ + private boolean isNearRedILine(float[] hslColor) { + return hslColor[0] >= 10f && hslColor[0] <= 37f && hslColor[1] <= 0.82f; + } + }; +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 815b3f9f40fc..5be9b50f743c 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5057,6 +5057,13 @@ + + + + + @@ -5067,6 +5074,10 @@ + + + + + + + + + + @string/live_display_auto + @string/live_display_off + @string/live_display_day + @string/live_display_night + @string/live_display_outdoor + + + + @string/live_display_auto_summary + @string/live_display_off_summary + @string/live_display_day_summary + @string/live_display_night_summary + @string/live_display_outdoor_summary + + + + 2 + 0 + 4 + 1 + 3 + + + + + @drawable/ic_livedisplay_auto + @drawable/ic_livedisplay_off + @drawable/ic_livedisplay_day + @drawable/ic_livedisplay_night + @drawable/ic_livedisplay_outdoor + + + + + @string/accessibility_quick_settings_live_display_auto + @string/accessibility_quick_settings_live_display_off + @string/accessibility_quick_settings_live_display_day + @string/accessibility_quick_settings_live_display_night + @string/accessibility_quick_settings_live_display_outdoor + + + + + @string/accessibility_quick_settings_live_display_changed_auto + @string/accessibility_quick_settings_live_display_changed_off + @string/accessibility_quick_settings_live_display_changed_day + @string/accessibility_quick_settings_live_display_changed_night + @string/accessibility_quick_settings_live_display_changed_outdoor + + + \ No newline at end of file diff --git a/core/res/res-revengeos/values/config.xml b/core/res/res-revengeos/values/config.xml index ede3e94fa06d..b1fff8b1d135 100644 --- a/core/res/res-revengeos/values/config.xml +++ b/core/res/res-revengeos/values/config.xml @@ -134,4 +134,35 @@ + + + 6500 + 4800 + 12000 + 1500 + 2 + + + 1000 + 10000 + + false + true + true + true + + + + + + + false diff --git a/core/res/res-revengeos/values/strings.xml b/core/res/res-revengeos/values/strings.xml index 862cb3c7fbb1..52786f6942c2 100644 --- a/core/res/res-revengeos/values/strings.xml +++ b/core/res/res-revengeos/values/strings.xml @@ -20,5 +20,29 @@ Press and hold power button to unlock + + + LiveDisplay + Automatic + Automatically adjust color temperature of screen after sunset and sunrise + Off + Disable all adjustments + Day + Use day settings only + Night + Use night settings only + Outdoor (bright sun) + Use outdoor settings only + LiveDisplay can help reduce eyestrain and help you sleep at night. Click here to try it out! + LiveDisplay off. + LiveDisplay: auto mode. + LiveDisplay: day mode. + LiveDisplay: night mode. + LiveDisplay: outdoor mode. + LiveDisplay turned off. + LiveDisplay changed to auto mode. + LiveDisplay changed to day mode. + LiveDisplay changed to night mode. + LiveDisplay changed to outdoor mode. \ No newline at end of file diff --git a/core/res/res-revengeos/values/symbols.xml b/core/res/res-revengeos/values/symbols.xml index 7acdc2d7d92f..57260333aff0 100644 --- a/core/res/res-revengeos/values/symbols.xml +++ b/core/res/res-revengeos/values/symbols.xml @@ -59,4 +59,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/res/res/drawable/ic_livedisplay_auto.xml b/core/res/res/drawable/ic_livedisplay_auto.xml new file mode 100644 index 000000000000..39d1f8cc6205 --- /dev/null +++ b/core/res/res/drawable/ic_livedisplay_auto.xml @@ -0,0 +1,29 @@ + + + + + + \ No newline at end of file diff --git a/core/res/res/drawable/ic_livedisplay_day.xml b/core/res/res/drawable/ic_livedisplay_day.xml new file mode 100644 index 000000000000..f454d81f6d4b --- /dev/null +++ b/core/res/res/drawable/ic_livedisplay_day.xml @@ -0,0 +1,30 @@ + + + + + + \ No newline at end of file diff --git a/core/res/res/drawable/ic_livedisplay_night.xml b/core/res/res/drawable/ic_livedisplay_night.xml new file mode 100644 index 000000000000..88a6764c748c --- /dev/null +++ b/core/res/res/drawable/ic_livedisplay_night.xml @@ -0,0 +1,29 @@ + + + + + + \ No newline at end of file diff --git a/core/res/res/drawable/ic_livedisplay_notif.xml b/core/res/res/drawable/ic_livedisplay_notif.xml new file mode 100644 index 000000000000..a7cb8c69646b --- /dev/null +++ b/core/res/res/drawable/ic_livedisplay_notif.xml @@ -0,0 +1,29 @@ + + + + + + \ No newline at end of file diff --git a/core/res/res/drawable/ic_livedisplay_off.xml b/core/res/res/drawable/ic_livedisplay_off.xml new file mode 100644 index 000000000000..f454d81f6d4b --- /dev/null +++ b/core/res/res/drawable/ic_livedisplay_off.xml @@ -0,0 +1,30 @@ + + + + + + \ No newline at end of file diff --git a/core/res/res/drawable/ic_livedisplay_outdoor.xml b/core/res/res/drawable/ic_livedisplay_outdoor.xml new file mode 100644 index 000000000000..66ead51185c2 --- /dev/null +++ b/core/res/res/drawable/ic_livedisplay_outdoor.xml @@ -0,0 +1,27 @@ + + + + + + \ No newline at end of file diff --git a/services/core/java/com/android/server/custom/LineageHardwareService.java b/services/core/java/com/android/server/custom/LineageHardwareService.java new file mode 100644 index 000000000000..c5d6b18251bd --- /dev/null +++ b/services/core/java/com/android/server/custom/LineageHardwareService.java @@ -0,0 +1,568 @@ +/* + * Copyright (C) 2015-2016 The CyanogenMod Project + * 2017-2019 The LineageOS Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.custom; + +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; +import android.util.Range; + +import com.android.server.display.color.DisplayTransformManager; +import com.android.server.LocalServices; +import com.android.server.SystemService; + +import com.android.internal.custom.hardware.ILineageHardwareService; +import com.android.internal.custom.hardware.LineageHardwareManager; +import com.android.internal.custom.hardware.DisplayMode; +import com.android.internal.custom.hardware.HSIC; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.android.internal.custom.app.LineageContextConstants; +import com.android.internal.custom.hardware.AdaptiveBacklight; +import com.android.internal.custom.hardware.AutoContrast; +import com.android.internal.custom.hardware.ColorBalance; +import com.android.internal.custom.hardware.ColorEnhancement; +import com.android.internal.custom.hardware.DisplayColorCalibration; +import com.android.internal.custom.hardware.DisplayModeControl; +import com.android.internal.custom.hardware.PictureAdjustment; +import com.android.internal.custom.hardware.ReadingEnhancement; +import com.android.internal.custom.hardware.SunlightEnhancement; + +import static com.android.server.display.color.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY; +import static com.android.server.display.color.DisplayTransformManager.LEVEL_COLOR_MATRIX_GRAYSCALE; + +/** @hide */ +public class LineageHardwareService extends SystemService { + + private static final boolean DEBUG = true; + private static final String TAG = LineageHardwareService.class.getSimpleName(); + + private final Context mContext; + private final LineageHardwareInterface mLineageHwImpl; + + private interface LineageHardwareInterface { + public int getSupportedFeatures(); + public boolean get(int feature); + public boolean set(int feature, boolean enable); + + public int[] getDisplayColorCalibration(); + public boolean setDisplayColorCalibration(int[] rgb); + + public boolean requireAdaptiveBacklightForSunlightEnhancement(); + public boolean isSunlightEnhancementSelfManaged(); + + public DisplayMode[] getDisplayModes(); + public DisplayMode getCurrentDisplayMode(); + public DisplayMode getDefaultDisplayMode(); + public boolean setDisplayMode(DisplayMode mode, boolean makeDefault); + + public int getColorBalanceMin(); + public int getColorBalanceMax(); + public int getColorBalance(); + public boolean setColorBalance(int value); + + public HSIC getPictureAdjustment(); + public HSIC getDefaultPictureAdjustment(); + public boolean setPictureAdjustment(HSIC hsic); + public List> getPictureAdjustmentRanges(); + } + + private class LegacyLineageHardware implements LineageHardwareInterface { + + private final int MIN = 0; + private final int MAX = 255; + + /** + * Matrix and offset used for converting color to grayscale. + * Copied from com.android.server.accessibility.DisplayAdjustmentUtils.MATRIX_GRAYSCALE + */ + private final float[] MATRIX_GRAYSCALE = { + .2126f, .2126f, .2126f, 0, + .7152f, .7152f, .7152f, 0, + .0722f, .0722f, .0722f, 0, + 0, 0, 0, 1 + }; + + /** Full color matrix and offset */ + private final float[] MATRIX_NORMAL = { + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + }; + + private final int LEVEL_COLOR_MATRIX_CALIB = LEVEL_COLOR_MATRIX_NIGHT_DISPLAY + 1; + private final int LEVEL_COLOR_MATRIX_READING = LEVEL_COLOR_MATRIX_GRAYSCALE + 1; + + private boolean mAcceleratedTransform; + private DisplayTransformManager mDTMService; + + private int[] mCurColors = { MAX, MAX, MAX }; + private boolean mReadingEnhancementEnabled; + + private int mSupportedFeatures = 0; + + public LegacyLineageHardware() { + mAcceleratedTransform = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_setColorTransformAccelerated); + if (AdaptiveBacklight.isSupported()) + mSupportedFeatures |= LineageHardwareManager.FEATURE_ADAPTIVE_BACKLIGHT; + if (ColorEnhancement.isSupported()) + mSupportedFeatures |= LineageHardwareManager.FEATURE_COLOR_ENHANCEMENT; + if (DisplayColorCalibration.isSupported()) + mSupportedFeatures |= LineageHardwareManager.FEATURE_DISPLAY_COLOR_CALIBRATION; + if (ReadingEnhancement.isSupported()) + mSupportedFeatures |= LineageHardwareManager.FEATURE_READING_ENHANCEMENT; + if (SunlightEnhancement.isSupported()) + mSupportedFeatures |= LineageHardwareManager.FEATURE_SUNLIGHT_ENHANCEMENT; + if (AutoContrast.isSupported()) + mSupportedFeatures |= LineageHardwareManager.FEATURE_AUTO_CONTRAST; + if (DisplayModeControl.isSupported()) + mSupportedFeatures |= LineageHardwareManager.FEATURE_DISPLAY_MODES; + if (ColorBalance.isSupported()) + mSupportedFeatures |= LineageHardwareManager.FEATURE_COLOR_BALANCE; + if (PictureAdjustment.isSupported()) + mSupportedFeatures |= LineageHardwareManager.FEATURE_PICTURE_ADJUSTMENT; + if (mAcceleratedTransform) { + mDTMService = LocalServices.getService(DisplayTransformManager.class); + mSupportedFeatures |= LineageHardwareManager.FEATURE_DISPLAY_COLOR_CALIBRATION; + mSupportedFeatures |= LineageHardwareManager.FEATURE_READING_ENHANCEMENT; + } + } + + public int getSupportedFeatures() { + return mSupportedFeatures; + } + + public boolean get(int feature) { + switch(feature) { + case LineageHardwareManager.FEATURE_ADAPTIVE_BACKLIGHT: + return AdaptiveBacklight.isEnabled(); + case LineageHardwareManager.FEATURE_AUTO_CONTRAST: + return AutoContrast.isEnabled(); + case LineageHardwareManager.FEATURE_COLOR_ENHANCEMENT: + return ColorEnhancement.isEnabled(); + case LineageHardwareManager.FEATURE_READING_ENHANCEMENT: + if (mAcceleratedTransform) + return mReadingEnhancementEnabled; + return ReadingEnhancement.isEnabled(); + case LineageHardwareManager.FEATURE_SUNLIGHT_ENHANCEMENT: + return SunlightEnhancement.isEnabled(); + default: + Log.e(TAG, "feature " + feature + " is not a boolean feature"); + return false; + } + } + + public boolean set(int feature, boolean enable) { + switch(feature) { + case LineageHardwareManager.FEATURE_ADAPTIVE_BACKLIGHT: + return AdaptiveBacklight.setEnabled(enable); + case LineageHardwareManager.FEATURE_AUTO_CONTRAST: + return AutoContrast.setEnabled(enable); + case LineageHardwareManager.FEATURE_COLOR_ENHANCEMENT: + return ColorEnhancement.setEnabled(enable); + case LineageHardwareManager.FEATURE_READING_ENHANCEMENT: + if (mAcceleratedTransform) { + mReadingEnhancementEnabled = enable; + mDTMService.setColorMatrix(LEVEL_COLOR_MATRIX_READING, + enable ? MATRIX_GRAYSCALE : MATRIX_NORMAL); + return true; + } + return ReadingEnhancement.setEnabled(enable); + case LineageHardwareManager.FEATURE_SUNLIGHT_ENHANCEMENT: + return SunlightEnhancement.setEnabled(enable); + default: + Log.e(TAG, "feature " + feature + " is not a boolean feature"); + return false; + } + } + + private int[] splitStringToInt(String input, String delimiter) { + if (input == null || delimiter == null) { + return null; + } + String strArray[] = input.split(delimiter); + try { + int intArray[] = new int[strArray.length]; + for(int i = 0; i < strArray.length; i++) { + intArray[i] = Integer.parseInt(strArray[i]); + } + return intArray; + } catch (NumberFormatException e) { + /* ignore */ + } + return null; + } + + private String rgbToString(int[] rgb) { + StringBuilder builder = new StringBuilder(); + builder.append(rgb[LineageHardwareManager.COLOR_CALIBRATION_RED_INDEX]); + builder.append(" "); + builder.append(rgb[LineageHardwareManager.COLOR_CALIBRATION_GREEN_INDEX]); + builder.append(" "); + builder.append(rgb[LineageHardwareManager.COLOR_CALIBRATION_BLUE_INDEX]); + return builder.toString(); + } + + private float[] rgbToMatrix(int[] rgb) { + float[] mat = new float[16]; + + for (int i = 0; i < 3; i++) { + // Sanity check + if (rgb[i] > MAX) + rgb[i] = MAX; + else if (rgb[i] < MIN) + rgb[i] = MIN; + + mat[i * 5] = (float)rgb[i] / (float)MAX; + } + + mat[15] = 1.0f; + return mat; + } + + public int[] getDisplayColorCalibration() { + int[] rgb = mAcceleratedTransform ? mCurColors : + splitStringToInt(DisplayColorCalibration.getCurColors(), " "); + if (rgb == null || rgb.length != 3) { + Log.e(TAG, "Invalid color calibration string"); + return null; + } + int[] currentCalibration = new int[5]; + currentCalibration[LineageHardwareManager.COLOR_CALIBRATION_RED_INDEX] = rgb[0]; + currentCalibration[LineageHardwareManager.COLOR_CALIBRATION_GREEN_INDEX] = rgb[1]; + currentCalibration[LineageHardwareManager.COLOR_CALIBRATION_BLUE_INDEX] = rgb[2]; + currentCalibration[LineageHardwareManager.COLOR_CALIBRATION_MIN_INDEX] = + mAcceleratedTransform ? MIN : DisplayColorCalibration.getMinValue(); + currentCalibration[LineageHardwareManager.COLOR_CALIBRATION_MAX_INDEX] = + mAcceleratedTransform ? MAX : DisplayColorCalibration.getMaxValue(); + return currentCalibration; + } + + public boolean setDisplayColorCalibration(int[] rgb) { + if (mAcceleratedTransform) { + mCurColors = rgb; + mDTMService.setColorMatrix(LEVEL_COLOR_MATRIX_CALIB, rgbToMatrix(rgb)); + return true; + } + return DisplayColorCalibration.setColors(rgbToString(rgb)); + } + + public boolean requireAdaptiveBacklightForSunlightEnhancement() { + return SunlightEnhancement.isAdaptiveBacklightRequired(); + } + + public boolean isSunlightEnhancementSelfManaged() { + return SunlightEnhancement.isSelfManaged(); + } + + public DisplayMode[] getDisplayModes() { + return DisplayModeControl.getAvailableModes(); + } + + public DisplayMode getCurrentDisplayMode() { + return DisplayModeControl.getCurrentMode(); + } + + public DisplayMode getDefaultDisplayMode() { + return DisplayModeControl.getDefaultMode(); + } + + public boolean setDisplayMode(DisplayMode mode, boolean makeDefault) { + return DisplayModeControl.setMode(mode, makeDefault); + } + + public int getColorBalanceMin() { + return ColorBalance.getMinValue(); + } + + public int getColorBalanceMax() { + return ColorBalance.getMaxValue(); + } + + public int getColorBalance() { + return ColorBalance.getValue(); + } + + public boolean setColorBalance(int value) { + return ColorBalance.setValue(value); + } + + public HSIC getPictureAdjustment() { return PictureAdjustment.getHSIC(); } + + public HSIC getDefaultPictureAdjustment() { return PictureAdjustment.getDefaultHSIC(); } + + public boolean setPictureAdjustment(HSIC hsic) { return PictureAdjustment.setHSIC(hsic); } + + public List> getPictureAdjustmentRanges() { + return Arrays.asList( + PictureAdjustment.getHueRange(), + PictureAdjustment.getSaturationRange(), + PictureAdjustment.getIntensityRange(), + PictureAdjustment.getContrastRange(), + PictureAdjustment.getSaturationThresholdRange()); + } + } + + private LineageHardwareInterface getImpl(Context context) { + return new LegacyLineageHardware(); + } + + public LineageHardwareService(Context context) { + super(context); + mContext = context; + mLineageHwImpl = getImpl(context); + publishBinderService(LineageContextConstants.LINEAGE_HARDWARE_SERVICE, mService); + } + + @Override + public void onBootPhase(int phase) { + if (phase == PHASE_BOOT_COMPLETED) { + Intent intent = new Intent("lineageos.intent.action.INITIALIZE_LINEAGE_HARDWARE"); + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL, + "lineageos.permission.HARDWARE_ABSTRACTION_ACCESS"); + } + } + + @Override + public void onStart() { + } + + private final IBinder mService = new ILineageHardwareService.Stub() { + + private boolean isSupported(int feature) { + return (getSupportedFeatures() & feature) == feature; + } + + @Override + public int getSupportedFeatures() { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.HARDWARE_ABSTRACTION_ACCESS", null); + return mLineageHwImpl.getSupportedFeatures(); + } + + @Override + public boolean get(int feature) { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.HARDWARE_ABSTRACTION_ACCESS", null); + if (!isSupported(feature)) { + Log.e(TAG, "feature " + feature + " is not supported"); + return false; + } + return mLineageHwImpl.get(feature); + } + + @Override + public boolean set(int feature, boolean enable) { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.HARDWARE_ABSTRACTION_ACCESS", null); + if (!isSupported(feature)) { + Log.e(TAG, "feature " + feature + " is not supported"); + return false; + } + return mLineageHwImpl.set(feature, enable); + } + + @Override + public int[] getDisplayColorCalibration() { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.HARDWARE_ABSTRACTION_ACCESS", null); + if (!isSupported(LineageHardwareManager.FEATURE_DISPLAY_COLOR_CALIBRATION)) { + Log.e(TAG, "Display color calibration is not supported"); + return null; + } + return mLineageHwImpl.getDisplayColorCalibration(); + } + + @Override + public boolean setDisplayColorCalibration(int[] rgb) { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.HARDWARE_ABSTRACTION_ACCESS", null); + if (!isSupported(LineageHardwareManager.FEATURE_DISPLAY_COLOR_CALIBRATION)) { + Log.e(TAG, "Display color calibration is not supported"); + return false; + } + if (rgb.length < 3) { + Log.e(TAG, "Invalid color calibration"); + return false; + } + return mLineageHwImpl.setDisplayColorCalibration(rgb); + } + + @Override + public boolean requireAdaptiveBacklightForSunlightEnhancement() { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.HARDWARE_ABSTRACTION_ACCESS", null); + if (!isSupported(LineageHardwareManager.FEATURE_SUNLIGHT_ENHANCEMENT)) { + Log.e(TAG, "Sunlight enhancement is not supported"); + return false; + } + return mLineageHwImpl.requireAdaptiveBacklightForSunlightEnhancement(); + } + + @Override + public boolean isSunlightEnhancementSelfManaged() { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.HARDWARE_ABSTRACTION_ACCESS", null); + if (!isSupported(LineageHardwareManager.FEATURE_SUNLIGHT_ENHANCEMENT)) { + Log.e(TAG, "Sunlight enhancement is not supported"); + return false; + } + return mLineageHwImpl.isSunlightEnhancementSelfManaged(); + } + + @Override + public DisplayMode[] getDisplayModes() { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.HARDWARE_ABSTRACTION_ACCESS", null); + if (!isSupported(LineageHardwareManager.FEATURE_DISPLAY_MODES)) { + Log.e(TAG, "Display modes are not supported"); + return null; + } + return mLineageHwImpl.getDisplayModes(); + } + + @Override + public DisplayMode getCurrentDisplayMode() { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.HARDWARE_ABSTRACTION_ACCESS", null); + if (!isSupported(LineageHardwareManager.FEATURE_DISPLAY_MODES)) { + Log.e(TAG, "Display modes are not supported"); + return null; + } + return mLineageHwImpl.getCurrentDisplayMode(); + } + + @Override + public DisplayMode getDefaultDisplayMode() { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.HARDWARE_ABSTRACTION_ACCESS", null); + if (!isSupported(LineageHardwareManager.FEATURE_DISPLAY_MODES)) { + Log.e(TAG, "Display modes are not supported"); + return null; + } + return mLineageHwImpl.getDefaultDisplayMode(); + } + + @Override + public boolean setDisplayMode(DisplayMode mode, boolean makeDefault) { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.HARDWARE_ABSTRACTION_ACCESS", null); + if (!isSupported(LineageHardwareManager.FEATURE_DISPLAY_MODES)) { + Log.e(TAG, "Display modes are not supported"); + return false; + } + return mLineageHwImpl.setDisplayMode(mode, makeDefault); + } + + @Override + public int getColorBalanceMin() { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.HARDWARE_ABSTRACTION_ACCESS", null); + if (isSupported(LineageHardwareManager.FEATURE_COLOR_BALANCE)) { + return mLineageHwImpl.getColorBalanceMin(); + } + return 0; + } + + @Override + public int getColorBalanceMax() { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.HARDWARE_ABSTRACTION_ACCESS", null); + if (isSupported(LineageHardwareManager.FEATURE_COLOR_BALANCE)) { + return mLineageHwImpl.getColorBalanceMax(); + } + return 0; + } + + @Override + public int getColorBalance() { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.HARDWARE_ABSTRACTION_ACCESS", null); + if (isSupported(LineageHardwareManager.FEATURE_COLOR_BALANCE)) { + return mLineageHwImpl.getColorBalance(); + } + return 0; + } + + @Override + public boolean setColorBalance(int value) { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.HARDWARE_ABSTRACTION_ACCESS", null); + if (isSupported(LineageHardwareManager.FEATURE_COLOR_BALANCE)) { + return mLineageHwImpl.setColorBalance(value); + } + return false; + } + + @Override + public HSIC getPictureAdjustment() { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.HARDWARE_ABSTRACTION_ACCESS", null); + if (isSupported(LineageHardwareManager.FEATURE_PICTURE_ADJUSTMENT)) { + return mLineageHwImpl.getPictureAdjustment(); + } + return new HSIC(0.0f, 0.0f, 0.0f, 0.0f, 0.0f); + } + + @Override + public HSIC getDefaultPictureAdjustment() { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.HARDWARE_ABSTRACTION_ACCESS", null); + if (isSupported(LineageHardwareManager.FEATURE_PICTURE_ADJUSTMENT)) { + return mLineageHwImpl.getDefaultPictureAdjustment(); + } + return new HSIC(0.0f, 0.0f, 0.0f, 0.0f, 0.0f); + } + + @Override + public boolean setPictureAdjustment(HSIC hsic) { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.HARDWARE_ABSTRACTION_ACCESS", null); + if (isSupported(LineageHardwareManager.FEATURE_PICTURE_ADJUSTMENT) && hsic != null) { + return mLineageHwImpl.setPictureAdjustment(hsic); + } + return false; + } + + @Override + public float[] getPictureAdjustmentRanges() { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.HARDWARE_ABSTRACTION_ACCESS", null); + if (isSupported(LineageHardwareManager.FEATURE_PICTURE_ADJUSTMENT)) { + final List> r = mLineageHwImpl.getPictureAdjustmentRanges(); + return new float[] { + r.get(0).getLower(), r.get(0).getUpper(), + r.get(1).getLower(), r.get(1).getUpper(), + r.get(2).getLower(), r.get(2).getUpper(), + r.get(3).getLower(), r.get(3).getUpper(), + r.get(4).getUpper(), r.get(4).getUpper() }; + } + return new float[10]; + } + }; +} diff --git a/services/core/java/com/android/server/custom/common/UserContentObserver.java b/services/core/java/com/android/server/custom/common/UserContentObserver.java new file mode 100644 index 000000000000..f1902922eac2 --- /dev/null +++ b/services/core/java/com/android/server/custom/common/UserContentObserver.java @@ -0,0 +1,95 @@ +/* + * 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 com.android.server.custom.common; + +import android.app.ActivityManagerNative; +import android.app.IUserSwitchObserver; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.IRemoteCallback; +import android.os.RemoteException; +import android.util.Log; + +/** + * Simple extension of ContentObserver that also listens for user switch events to call update + */ +public abstract class UserContentObserver extends ContentObserver { + private static final String TAG = "UserContentObserver"; + + private Runnable mUpdateRunnable; + + private IUserSwitchObserver mUserSwitchObserver = new IUserSwitchObserver.Stub() { + @Override + public void onUserSwitching(int newUserId, IRemoteCallback reply) { + } + @Override + public void onUserSwitchComplete(int newUserId) throws RemoteException { + mHandler.post(mUpdateRunnable); + } + @Override + public void onForegroundProfileSwitch(int newProfileId) { + } + @Override + public void onLockedBootComplete(int newUserId) { + } + }; + + private Handler mHandler; + + /** + * Content observer that tracks user switches + * to allow clients to re-load settings for current user + */ + public UserContentObserver(Handler handler) { + super(handler); + mHandler = handler; + mUpdateRunnable = new Runnable() { + @Override + public void run() { + update(); + } + }; + } + + protected void observe() { + try { + ActivityManagerNative.getDefault().registerUserSwitchObserver(mUserSwitchObserver, TAG); + } catch (RemoteException e) { + Log.w(TAG, "Unable to register user switch observer!", e); + } + } + + protected void unobserve() { + try { + mHandler.removeCallbacks(mUpdateRunnable); + ActivityManagerNative.getDefault().unregisterUserSwitchObserver(mUserSwitchObserver); + } catch (RemoteException e) { + Log.w(TAG, "Unable to unregister user switch observer!", e); + } + } + + /** + * Called to notify of registered uri changes and user switches. + * Always invoked on the handler passed in at construction + */ + protected abstract void update(); + + @Override + public void onChange(boolean selfChange, Uri uri) { + update(); + } +} diff --git a/services/core/java/com/android/server/custom/display/AmbientLuxObserver.java b/services/core/java/com/android/server/custom/display/AmbientLuxObserver.java new file mode 100644 index 000000000000..cc6a7e206387 --- /dev/null +++ b/services/core/java/com/android/server/custom/display/AmbientLuxObserver.java @@ -0,0 +1,259 @@ +/* + * 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 com.android.server.custom.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 float mHysteresisLux; + 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, float hysteresisLux, int thresholdDuration) { + mLuxHandler = new AmbientLuxHandler(looper); + mThresholdLux = thresholdLux; + mHysteresisLux = hysteresisLux; + 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); + } + + final float threshold = mState == HIGH + ? mThresholdLux - mHysteresisLux : mThresholdLux; + direction = mAmbientLux >= threshold ? 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) { + removeMessages(MSG_TRANSITION); + 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) { + mLightSensorEnabled = true; + mSensorManager.registerListener(mListener, mLightSensor, + mLightSensorRate * 1000, mLuxHandler); + } else if (!enable && mLightSensorEnabled) { + mSensorManager.unregisterListener(mListener); + mLuxHandler.clear(); + mAmbientLux = 0.0f; + mState = LOW; + mLightSensorEnabled = false; + mRingBuffer.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/services/core/java/com/android/server/custom/display/ColorTemperatureController.java b/services/core/java/com/android/server/custom/display/ColorTemperatureController.java new file mode 100644 index 000000000000..7a05966b9be8 --- /dev/null +++ b/services/core/java/com/android/server/custom/display/ColorTemperatureController.java @@ -0,0 +1,372 @@ +/* + * 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 com.android.server.custom.display; + +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.content.Context; +import android.net.Uri; +import android.os.Handler; +import android.text.format.DateUtils; +import android.util.MathUtils; +import android.util.Range; +import android.util.Slog; +import android.view.animation.LinearInterpolator; + +import com.android.server.custom.display.TwilightTracker.TwilightState; + +import java.io.PrintWriter; +import java.util.BitSet; + +import com.android.internal.custom.hardware.LineageHardwareManager; +import com.android.internal.custom.hardware.LiveDisplayManager; +import android.provider.Settings; +import com.android.internal.util.custom.ColorUtils; + +import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_AUTO; +import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_DAY; +import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_NIGHT; +import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_OFF; + +public class ColorTemperatureController extends LiveDisplayFeature { + + private final DisplayHardwareController mDisplayHardware; + + private final boolean mUseTemperatureAdjustment; + private final boolean mUseColorBalance; + private final Range mColorBalanceRange; + private final Range mColorTemperatureRange; + private final double[] mColorBalanceCurve; + + private final int mDefaultDayTemperature; + private final int mDefaultNightTemperature; + + private int mColorTemperature = -1; + private int mDayTemperature; + private int mNightTemperature; + + private ValueAnimator mAnimator; + + private final LineageHardwareManager mHardware; + + private static final long TWILIGHT_ADJUSTMENT_TIME = DateUtils.HOUR_IN_MILLIS / 2; + + private static final Uri DISPLAY_TEMPERATURE_DAY = + Settings.System.getUriFor(Settings.System.DISPLAY_TEMPERATURE_DAY); + private static final Uri DISPLAY_TEMPERATURE_NIGHT = + Settings.System.getUriFor(Settings.System.DISPLAY_TEMPERATURE_NIGHT); + + public ColorTemperatureController(Context context, + Handler handler, DisplayHardwareController displayHardware) { + super(context, handler); + mDisplayHardware = displayHardware; + mHardware = LineageHardwareManager.getInstance(mContext); + + mUseColorBalance = mHardware + .isSupported(LineageHardwareManager.FEATURE_COLOR_BALANCE); + mColorBalanceRange = mHardware.getColorBalanceRange(); + + mUseTemperatureAdjustment = mUseColorBalance || + mDisplayHardware.hasColorAdjustment(); + + mDefaultDayTemperature = mContext.getResources().getInteger( + com.android.internal.R.integer.config_dayColorTemperature); + mDefaultNightTemperature = mContext.getResources().getInteger( + com.android.internal.R.integer.config_nightColorTemperature); + + mColorTemperatureRange = Range.create( + mContext.getResources().getInteger( + com.android.internal.R.integer.config_minColorTemperature), + mContext.getResources().getInteger( + com.android.internal.R.integer.config_maxColorTemperature)); + + mColorBalanceCurve = com.android.internal.util.custom.MathUtils.powerCurve( + mColorTemperatureRange.getLower(), + mDefaultDayTemperature, + mColorTemperatureRange.getUpper()); + } + + @Override + public void onStart() { + if (!mUseTemperatureAdjustment) { + return; + } + + mDayTemperature = getDayColorTemperature(); + mNightTemperature = getNightColorTemperature(); + + registerSettings(DISPLAY_TEMPERATURE_DAY, DISPLAY_TEMPERATURE_NIGHT); + } + + @Override + public boolean getCapabilities(final BitSet caps) { + if (mUseTemperatureAdjustment) { + caps.set(MODE_AUTO); + caps.set(MODE_DAY); + caps.set(MODE_NIGHT); + if (mUseColorBalance) { + caps.set(LiveDisplayManager.FEATURE_COLOR_BALANCE); + } + } + return mUseTemperatureAdjustment; + } + + @Override + protected void onUpdate() { + updateColorTemperature(); + } + + @Override + protected void onScreenStateChanged() { + if (mAnimator != null && mAnimator.isRunning() && !isScreenOn()) { + mAnimator.cancel(); + } else { + updateColorTemperature(); + } + } + + @Override + protected void onTwilightUpdated() { + updateColorTemperature(); + } + + @Override + protected synchronized void onSettingsChanged(Uri uri) { + if (uri == null || uri.equals(DISPLAY_TEMPERATURE_DAY)) { + mDayTemperature = getDayColorTemperature(); + } + if (uri == null || uri.equals(DISPLAY_TEMPERATURE_NIGHT)) { + mNightTemperature = getNightColorTemperature(); + } + updateColorTemperature(); + } + + @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); + pw.println(" isTransitioning=" + isTransitioning()); + } + + private final Runnable mTransitionRunnable = new Runnable() { + @Override + public void run() { + updateColorTemperature(); + } + }; + + private boolean isTransitioning() { + return getMode() == MODE_AUTO && + mColorTemperature != mDayTemperature && + mColorTemperature != mNightTemperature; + } + + private synchronized void updateColorTemperature() { + if (!mUseTemperatureAdjustment || !isScreenOn()) { + return; + } + int temperature = mDayTemperature; + int mode = getMode(); + + if (mode == MODE_OFF) { + temperature = mDefaultDayTemperature; + } else if (mode == MODE_NIGHT) { + temperature = mNightTemperature; + } else if (mode == MODE_AUTO) { + temperature = getTwilightK(); + } + + if (DEBUG) { + Slog.d(TAG, "updateColorTemperature mode=" + mode + + " temperature=" + temperature + " mColorTemperature=" + mColorTemperature); + } + + setDisplayTemperature(temperature); + + if (isTransitioning()) { + // fire again in a minute + mHandler.postDelayed(mTransitionRunnable, DateUtils.MINUTE_IN_MILLIS); + } + } + + /** + * Smoothly animate the current display color balance + */ + private synchronized void animateColorBalance(int balance) { + + // always start with the current values in the hardware + int current = mHardware.getColorBalance(); + + if (current == balance) { + return; + } + + long duration = (long)(5 * Math.abs(current - balance)); + + + if (DEBUG) { + Slog.d(TAG, "animateDisplayColor current=" + current + + " target=" + balance + " duration=" + duration); + } + + if (mAnimator != null) { + mAnimator.cancel(); + mAnimator.removeAllUpdateListeners(); + } + + mAnimator = ValueAnimator.ofInt(current, balance); + mAnimator.setDuration(duration); + mAnimator.setInterpolator(new LinearInterpolator()); + mAnimator.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(final ValueAnimator animation) { + synchronized (ColorTemperatureController.this) { + if (isScreenOn()) { + int value = (int) animation.getAnimatedValue(); + mHardware.setColorBalance(value); + } + } + } + }); + mAnimator.start(); + } + + /* + * Map the color temperature to a color balance value using a power curve. This assumes the + * correct configuration at the device level! + */ + private int mapColorTemperatureToBalance(int temperature) { + double z = com.android.internal.util.custom.MathUtils.powerCurveToLinear(mColorBalanceCurve, temperature); + return Math.round(MathUtils.lerp((float)mColorBalanceRange.getLower(), + (float)mColorBalanceRange.getUpper(), (float)z)); + } + + private synchronized void setDisplayTemperature(int temperature) { + if (!mColorTemperatureRange.contains(temperature)) { + Slog.e(TAG, "Color temperature out of range: " + temperature); + return; + } + + mColorTemperature = temperature; + + if (mUseColorBalance) { + int balance = mapColorTemperatureToBalance(temperature); + Slog.d(TAG, "Set color balance = " + balance + " (temperature=" + temperature + ")"); + animateColorBalance(balance); + return; + } + + final float[] rgb = ColorUtils.temperatureToRGB(temperature); + if (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 - TWILIGHT_ADJUSTMENT_TIME) + || now > (sunrise + TWILIGHT_ADJUSTMENT_TIME)) { + // More than 0.5hr after civil sunrise or before civil sunset + return 1.0f; + } + + // Scale the transition into night mode in 0.5hr before civil sunset + if (now <= sunset) { + return (float) (sunset - now) / TWILIGHT_ADJUSTMENT_TIME; + } + + // Scale the transition into day mode in 0.5hr after civil sunrise + if (now >= sunrise) { + return (float) (now - sunrise) / TWILIGHT_ADJUSTMENT_TIME; + } + + // More than 0.5hr past civil sunset + 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); + } + + int getDefaultDayTemperature() { + return mDefaultDayTemperature; + } + + int getDefaultNightTemperature() { + return mDefaultNightTemperature; + } + + int getColorTemperature() { + return mColorTemperature; + } + + int getDayColorTemperature() { + return getInt(Settings.System.DISPLAY_TEMPERATURE_DAY, + mDefaultDayTemperature); + } + + void setDayColorTemperature(int temperature) { + putInt(Settings.System.DISPLAY_TEMPERATURE_DAY, temperature); + } + + int getNightColorTemperature() { + return getInt(Settings.System.DISPLAY_TEMPERATURE_NIGHT, + mDefaultNightTemperature); + } + + void setNightColorTemperature(int temperature) { + putInt(Settings.System.DISPLAY_TEMPERATURE_NIGHT, temperature); + } + + Range getColorTemperatureRange() { + return mColorTemperatureRange; + } + + Range getColorBalanceRange() { + return mColorBalanceRange; + } +} diff --git a/services/core/java/com/android/server/custom/display/DisplayHardwareController.java b/services/core/java/com/android/server/custom/display/DisplayHardwareController.java new file mode 100644 index 000000000000..3a6476fc6f12 --- /dev/null +++ b/services/core/java/com/android/server/custom/display/DisplayHardwareController.java @@ -0,0 +1,512 @@ +/* + * Copyright (C) 2016 The CyanogenMod Project + * 2018 The LineageOS Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.custom.display; + +import android.animation.FloatArrayEvaluator; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.content.Context; +import android.net.Uri; +import android.os.Handler; +import android.os.IBinder; +import android.os.Parcel; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.MathUtils; +import android.util.Slog; +import android.view.animation.LinearInterpolator; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; + +import com.android.internal.custom.hardware.LineageHardwareManager; +import com.android.internal.custom.hardware.LiveDisplayManager; +import android.provider.Settings; + +public class DisplayHardwareController extends LiveDisplayFeature { + + private final LineageHardwareManager mHardware; + + // hardware capabilities + private final boolean mUseAutoContrast; + private final boolean mUseColorAdjustment; + private final boolean mUseColorEnhancement; + private final boolean mUseCABC; + private final boolean mUseReaderMode; + private final boolean mUseDisplayModes; + + // default values + private final boolean mDefaultAutoContrast; + private final boolean mDefaultColorEnhancement; + private final boolean mDefaultCABC; + + // color adjustment holders + private final float[] mAdditionalAdjustment = getDefaultAdjustment(); + private final float[] mColorAdjustment = getDefaultAdjustment(); + + private ValueAnimator mAnimator; + + private final int mMaxColor; + + // settings uris + private static final Uri DISPLAY_AUTO_CONTRAST = + Settings.System.getUriFor(Settings.System.DISPLAY_AUTO_CONTRAST); + private static final Uri DISPLAY_COLOR_ADJUSTMENT = + Settings.System.getUriFor(Settings.System.DISPLAY_COLOR_ADJUSTMENT); + private static final Uri DISPLAY_COLOR_ENHANCE = + Settings.System.getUriFor(Settings.System.DISPLAY_COLOR_ENHANCE); + private static final Uri DISPLAY_CABC = + Settings.System.getUriFor(Settings.System.DISPLAY_CABC); + private static final Uri DISPLAY_READING_MODE = + Settings.System.getUriFor(Settings.System.DISPLAY_READING_MODE); + + public DisplayHardwareController(Context context, Handler handler) { + super(context, handler); + + mHardware = LineageHardwareManager.getInstance(mContext); + mUseCABC = mHardware + .isSupported(LineageHardwareManager.FEATURE_ADAPTIVE_BACKLIGHT); + mDefaultCABC = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_defaultCABC); + + mUseColorEnhancement = mHardware + .isSupported(LineageHardwareManager.FEATURE_COLOR_ENHANCEMENT); + mDefaultColorEnhancement = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_defaultColorEnhancement); + + mUseAutoContrast = mHardware + .isSupported(LineageHardwareManager.FEATURE_AUTO_CONTRAST); + mDefaultAutoContrast = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_defaultAutoContrast); + + mUseColorAdjustment = mHardware + .isSupported(LineageHardwareManager.FEATURE_DISPLAY_COLOR_CALIBRATION); + + mUseDisplayModes = mHardware + .isSupported(LineageHardwareManager.FEATURE_DISPLAY_MODES); + + mUseReaderMode = mHardware + .isSupported(LineageHardwareManager.FEATURE_READING_ENHANCEMENT); + + if (mUseColorAdjustment) { + mMaxColor = mHardware.getDisplayColorCalibrationMax(); + copyColors(getColorAdjustment(), mColorAdjustment); + } else { + mMaxColor = 0; + } + } + + @Override + public void onStart() { + final ArrayList settings = new ArrayList(); + + if (mUseCABC) { + settings.add(DISPLAY_CABC); + } + if (mUseColorEnhancement) { + settings.add(DISPLAY_COLOR_ENHANCE); + } + if (mUseAutoContrast) { + settings.add(DISPLAY_AUTO_CONTRAST); + } + if (mUseColorAdjustment) { + settings.add(DISPLAY_COLOR_ADJUSTMENT); + } + if (mUseReaderMode) { + settings.add(DISPLAY_READING_MODE); + } + + if (settings.size() == 0) { + return; + } + + registerSettings(settings.toArray(new Uri[0])); + } + + @Override + public boolean getCapabilities(final BitSet caps) { + if (mUseAutoContrast) { + caps.set(LiveDisplayManager.FEATURE_AUTO_CONTRAST); + } + if (mUseColorEnhancement) { + caps.set(LiveDisplayManager.FEATURE_COLOR_ENHANCEMENT); + } + if (mUseCABC) { + caps.set(LiveDisplayManager.FEATURE_CABC); + } + if (mUseColorAdjustment) { + caps.set(LiveDisplayManager.FEATURE_COLOR_ADJUSTMENT); + } + if (mUseDisplayModes) { + caps.set(LiveDisplayManager.FEATURE_DISPLAY_MODES); + } + if (mUseReaderMode) { + caps.set(LiveDisplayManager.FEATURE_READING_ENHANCEMENT); + } + return mUseAutoContrast || mUseColorEnhancement || mUseCABC || mUseColorAdjustment || + mUseDisplayModes || mUseReaderMode; + } + + @Override + public synchronized void onSettingsChanged(Uri uri) { + if (uri == null || uri.equals(DISPLAY_CABC)) { + updateCABCMode(); + } + if (uri == null || uri.equals(DISPLAY_AUTO_CONTRAST)) { + updateAutoContrast(); + } + if (uri == null || uri.equals(DISPLAY_COLOR_ENHANCE)) { + updateColorEnhancement(); + } + if (uri == null || uri.equals(DISPLAY_COLOR_ADJUSTMENT)) { + copyColors(getColorAdjustment(), mColorAdjustment); + updateColorAdjustment(); + } + } + + private synchronized void updateHardware() { + if (isScreenOn()) { + updateCABCMode(); + updateAutoContrast(); + updateColorEnhancement(); + } + } + + @Override + protected void onUpdate() { + updateHardware(); + } + + @Override + protected synchronized void onScreenStateChanged() { + if (mUseColorAdjustment) { + if (mAnimator != null && mAnimator.isRunning() && !isScreenOn()) { + mAnimator.cancel(); + } else if (isScreenOn()) { + updateColorAdjustment(); + } + } + } + + @Override + public void dump(PrintWriter pw) { + pw.println(); + pw.println("DisplayHardwareController Configuration:"); + pw.println(" mUseAutoContrast=" + mUseAutoContrast); + pw.println(" mUseColorAdjustment=" + mUseColorAdjustment); + pw.println(" mUseColorEnhancement=" + mUseColorEnhancement); + pw.println(" mUseCABC=" + mUseCABC); + pw.println(" mUseDisplayModes=" + mUseDisplayModes); + pw.println(" mUseReaderMode=" + mUseReaderMode); + pw.println(); + pw.println(" DisplayHardwareController State:"); + pw.println(" mAutoContrast=" + isAutoContrastEnabled()); + pw.println(" mColorEnhancement=" + isColorEnhancementEnabled()); + pw.println(" mCABC=" + isCABCEnabled()); + pw.println(" mColorAdjustment=" + Arrays.toString(mColorAdjustment)); + pw.println(" mAdditionalAdjustment=" + Arrays.toString(mAdditionalAdjustment)); + pw.println(" hardware setting=" + Arrays.toString(mHardware.getDisplayColorCalibration())); + } + + /** + * Automatic contrast optimization + */ + private void updateAutoContrast() { + if (!mUseAutoContrast) { + return; + } + mHardware.set(LineageHardwareManager.FEATURE_AUTO_CONTRAST, isAutoContrastEnabled()); + } + + /** + * Color enhancement is optional + */ + private void updateColorEnhancement() { + if (!mUseColorEnhancement) { + return; + } + mHardware.set(LineageHardwareManager.FEATURE_COLOR_ENHANCEMENT, + (!isLowPowerMode() || mDefaultColorEnhancement) && isColorEnhancementEnabled()); + } + + /** + * Adaptive backlight / low power mode. Turn it off when under very bright light. + */ + private void updateCABCMode() { + if (!mUseCABC) { + return; + } + mHardware.set(LineageHardwareManager.FEATURE_ADAPTIVE_BACKLIGHT, isCABCEnabled()); + } + + private synchronized void updateColorAdjustment() { + if (!mUseColorAdjustment) { + return; + } + + final float[] rgb = getDefaultAdjustment(); + + copyColors(mColorAdjustment, rgb); + rgb[0] *= mAdditionalAdjustment[0]; + rgb[1] *= mAdditionalAdjustment[1]; + rgb[2] *= mAdditionalAdjustment[2]; + + if (DEBUG) { + Slog.d(TAG, "updateColorAdjustment: " + Arrays.toString(rgb)); + } + + if (validateColors(rgb)) { + animateDisplayColor(rgb); + } + } + + /** + * Smoothly animate the current display colors to the new value. + */ + private synchronized void animateDisplayColor(float[] targetColors) { + + // always start with the current values in the hardware + int[] currentInts = mHardware.getDisplayColorCalibration(); + float[] currentColors = new float[] { + (float)currentInts[0] / (float)mMaxColor, + (float)currentInts[1] / (float)mMaxColor, + (float)currentInts[2] / (float)mMaxColor }; + + if (currentColors[0] == targetColors[0] && + currentColors[1] == targetColors[1] && + currentColors[2] == targetColors[2]) { + return; + } + + // max 500 ms, scaled vs. the largest delta + long duration = (long)(750 * (Math.max(Math.max( + Math.abs(currentColors[0] - targetColors[0]), + Math.abs(currentColors[1] - targetColors[1])), + Math.abs(currentColors[2] - targetColors[2])))); + + if (DEBUG) { + Slog.d(TAG, "animateDisplayColor current=" + Arrays.toString(currentColors) + + " targetColors=" + Arrays.toString(targetColors) + " duration=" + duration); + } + + if (mAnimator != null) { + mAnimator.cancel(); + mAnimator.removeAllUpdateListeners(); + } + + mAnimator = ValueAnimator.ofObject( + new FloatArrayEvaluator(new float[3]), currentColors, targetColors); + mAnimator.setDuration(duration); + mAnimator.setInterpolator(new LinearInterpolator()); + mAnimator.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(final ValueAnimator animation) { + synchronized (DisplayHardwareController.this) { + if (isScreenOn()) { + float[] value = (float[]) animation.getAnimatedValue(); + mHardware.setDisplayColorCalibration(new int[] { + (int) (value[0] * mMaxColor), + (int) (value[1] * mMaxColor), + (int) (value[2] * mMaxColor) + }); + screenRefresh(); + } + } + } + }); + mAnimator.start(); + } + + /** + * Tell SurfaceFlinger to repaint the screen. This is called after updating + * hardware registers for display calibration to have an immediate effect. + */ + private void screenRefresh() { + try { + final IBinder flinger = ServiceManager.getService("SurfaceFlinger"); + if (flinger != null) { + final Parcel data = Parcel.obtain(); + data.writeInterfaceToken("android.ui.ISurfaceComposer"); + flinger.transact(1004, data, null, 0); + data.recycle(); + } + } catch (RemoteException ex) { + Slog.e(TAG, "Failed to refresh screen", ex); + } + } + + /** + * Ensure all values are within range + * + * @param colors + * @return true if valid + */ + private boolean validateColors(float[] colors) { + if (colors == null || colors.length != 3) { + return false; + } + + for (int i = 0; i < 3; i++) { + colors[i] = MathUtils.constrain(colors[i], 0.0f, 1.0f); + } + return true; + } + + /** + * Parse and sanity check an RGB triplet from a string. + */ + private boolean parseColorAdjustment(String rgbString, float[] dest) { + String[] adj = rgbString == null ? null : rgbString.split(" "); + + if (adj == null || adj.length != 3 || dest == null || dest.length != 3) { + return false; + } + + try { + dest[0] = Float.parseFloat(adj[0]); + dest[1] = Float.parseFloat(adj[1]); + dest[2] = Float.parseFloat(adj[2]); + } catch (NumberFormatException e) { + Slog.e(TAG, e.getMessage(), e); + return false; + } + + // sanity check + return validateColors(dest); + } + + /** + * Additional adjustments provided by night mode + * + * @param adj + */ + synchronized boolean setAdditionalAdjustment(float[] adj) { + if (!mUseColorAdjustment) { + return false; + } + + if (DEBUG) { + Slog.d(TAG, "setAdditionalAdjustment: " + Arrays.toString(adj)); + } + + // Sanity check this so we don't mangle the display + if (validateColors(adj)) { + copyColors(adj, mAdditionalAdjustment); + updateColorAdjustment(); + return true; + } + return false; + } + + boolean getDefaultCABC() { + return mDefaultCABC; + } + + boolean getDefaultAutoContrast() { + return mDefaultAutoContrast; + } + + boolean getDefaultColorEnhancement() { + return mDefaultColorEnhancement; + } + + boolean isAutoContrastEnabled() { + return mUseAutoContrast && + getBoolean(Settings.System.DISPLAY_AUTO_CONTRAST, mDefaultAutoContrast); + } + + boolean setAutoContrastEnabled(boolean enabled) { + if (!mUseAutoContrast) { + return false; + } + putBoolean(Settings.System.DISPLAY_AUTO_CONTRAST, enabled); + return true; + } + + boolean isCABCEnabled() { + return mUseCABC && + getBoolean(Settings.System.DISPLAY_CABC, mDefaultCABC); + } + + boolean setCABCEnabled(boolean enabled) { + if (!mUseCABC) { + return false; + } + putBoolean(Settings.System.DISPLAY_CABC, enabled); + return true; + } + + boolean isColorEnhancementEnabled() { + return mUseColorEnhancement && + getBoolean(Settings.System.DISPLAY_COLOR_ENHANCE, + mDefaultColorEnhancement); + } + + boolean setColorEnhancementEnabled(boolean enabled) { + if (!mUseColorEnhancement) { + return false; + } + putBoolean(Settings.System.DISPLAY_COLOR_ENHANCE, enabled); + return true; + } + + float[] getColorAdjustment() { + if (!mUseColorAdjustment) { + return getDefaultAdjustment(); + } + float[] cur = new float[3]; + if (!parseColorAdjustment(getString(Settings.System.DISPLAY_COLOR_ADJUSTMENT), cur)) { + // clear it out if invalid + cur = getDefaultAdjustment(); + saveColorAdjustmentString(cur); + } + return cur; + } + + boolean setColorAdjustment(float[] adj) { + // sanity check + if (!mUseColorAdjustment || !validateColors(adj)) { + return false; + } + saveColorAdjustmentString(adj); + return true; + } + + private void saveColorAdjustmentString(final float[] adj) { + StringBuilder sb = new StringBuilder(); + sb.append(adj[0]).append(" ").append(adj[1]).append(" ").append(adj[2]); + putString(Settings.System.DISPLAY_COLOR_ADJUSTMENT, sb.toString()); + } + + boolean hasColorAdjustment() { + return mUseColorAdjustment; + } + + private static float[] getDefaultAdjustment() { + return new float[] { 1.0f, 1.0f, 1.0f }; + } + + private void copyColors(float[] src, float[] dst) { + if (src != null && dst != null && src.length == 3 && dst.length == 3) { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + } + } +} diff --git a/services/core/java/com/android/server/custom/display/LiveDisplayFeature.java b/services/core/java/com/android/server/custom/display/LiveDisplayFeature.java new file mode 100644 index 000000000000..c643428b94ee --- /dev/null +++ b/services/core/java/com/android/server/custom/display/LiveDisplayFeature.java @@ -0,0 +1,181 @@ +/* + * 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 com.android.server.custom.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.custom.common.UserContentObserver; +import com.android.server.custom.display.LiveDisplayService.State; +import com.android.server.custom.display.TwilightTracker.TwilightState; + +import java.io.PrintWriter; +import java.util.BitSet; + +import android.provider.Settings; + +import static com.android.server.custom.display.LiveDisplayService.ALL_CHANGED; +import static com.android.server.custom.display.LiveDisplayService.DISPLAY_CHANGED; +import static com.android.server.custom.display.LiveDisplayService.MODE_CHANGED; +import static com.android.server.custom.display.LiveDisplayService.TWILIGHT_CHANGED; + +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 SettingsObserver mSettingsObserver; + private State mState; + + public LiveDisplayFeature(Context context, Handler handler) { + mContext = context; + mHandler = handler; + } + + public abstract void onStart(); + + protected abstract void onSettingsChanged(Uri uri); + + public abstract void dump(PrintWriter pw); + + public abstract boolean getCapabilities(final BitSet caps); + + protected abstract void onUpdate(); + + void update(final int flags, final State state) { + mState = state; + if ((flags & DISPLAY_CHANGED) != 0) { + onScreenStateChanged(); + } + if (((flags & TWILIGHT_CHANGED) != 0) && mState.mTwilight != null) { + onTwilightUpdated(); + } + if ((flags & MODE_CHANGED) != 0) { + onUpdate(); + } + if (flags == ALL_CHANGED) { + onSettingsChanged(null); + } + } + + void start() { + if (mSettingsObserver == null) { + mSettingsObserver = new SettingsObserver(mHandler); + onStart(); + } + } + + public void onDestroy() { + mSettingsObserver.unregister(); + } + + protected void onScreenStateChanged() { } + + protected void onTwilightUpdated() { } + + protected final void registerSettings(Uri... settings) { + mSettingsObserver.register(settings); + } + + protected final boolean getBoolean(String setting, boolean defaultValue) { + return Settings.System.getIntForUser(mContext.getContentResolver(), + setting, (defaultValue ? 1 : 0), UserHandle.USER_CURRENT) == 1; + } + + protected final void putBoolean(String setting, boolean value) { + Settings.System.putIntForUser(mContext.getContentResolver(), + setting, (value ? 1 : 0), UserHandle.USER_CURRENT); + } + + protected final int getInt(String setting, int defaultValue) { + return Settings.System.getIntForUser(mContext.getContentResolver(), + setting, defaultValue, UserHandle.USER_CURRENT); + } + + protected final void putInt(String setting, int value) { + Settings.System.putIntForUser(mContext.getContentResolver(), + setting, value, UserHandle.USER_CURRENT); + } + + protected final String getString(String setting) { + return Settings.System.getStringForUser(mContext.getContentResolver(), + setting, UserHandle.USER_CURRENT); + } + + protected final void putString(String setting, String value) { + Settings.System.putStringForUser(mContext.getContentResolver(), + setting, value, UserHandle.USER_CURRENT); + } + + protected final boolean isLowPowerMode() { + return mState.mLowPowerMode; + } + + protected final int getMode() { + return mState.mMode; + } + + protected final boolean isScreenOn() { + return mState.mScreenOn; + } + + protected final TwilightState getTwilight() { + return mState.mTwilight; + } + + public final boolean isNight() { + return mState.mTwilight != null && mState.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/services/core/java/com/android/server/custom/display/LiveDisplayService.java b/services/core/java/com/android/server/custom/display/LiveDisplayService.java new file mode 100644 index 000000000000..c5be49af1519 --- /dev/null +++ b/services/core/java/com/android/server/custom/display/LiveDisplayService.java @@ -0,0 +1,557 @@ +/* + * Copyright (C) 2016 The CyanogenMod Project + * 2019 The LineageOS Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.custom.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.content.res.Resources; +import android.hardware.display.DisplayManager; +import android.net.Uri; +import android.os.Handler; +import android.os.IBinder; +import android.os.PowerManager.ServiceType; +import android.os.PowerManagerInternal; +import android.os.PowerSaveState; +import android.os.Process; +import android.os.UserHandle; +import android.view.Display; + +import com.android.server.LocalServices; +import com.android.server.ServiceThread; +import com.android.server.SystemService; + +import com.android.server.custom.common.UserContentObserver; +import com.android.server.custom.display.TwilightTracker.TwilightListener; +import com.android.server.custom.display.TwilightTracker.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 java.util.Locale; + +import com.android.internal.custom.app.LineageContextConstants; +import com.android.internal.custom.hardware.HSIC; +import com.android.internal.custom.hardware.ILiveDisplayService; +import com.android.internal.custom.hardware.LiveDisplayConfig; +import android.provider.Settings; + +import static com.android.internal.custom.hardware.LiveDisplayManager.FEATURE_MANAGED_OUTDOOR_MODE; +import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_DAY; +import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_FIRST; +import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_LAST; +import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_OFF; +import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_OUTDOOR; + +/** + * 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 LineageHardwareService to relay + * changes down to the lower layers. + */ +public class LiveDisplayService extends SystemService { + + private static final String TAG = "LiveDisplay"; + + private final Context mContext; + private final Handler mHandler; + private final ServiceThread mHandlerThread; + + private DisplayManager mDisplayManager; + private ModeObserver mModeObserver; + private final TwilightTracker mTwilightTracker; + + private boolean mAwaitingNudge = true; + private boolean mSunset = false; + + private final List mFeatures = new ArrayList(); + + private ColorTemperatureController mCTC; + private DisplayHardwareController mDHC; + private OutdoorModeController mOMC; + private PictureAdjustmentController mPAC; + + private LiveDisplayConfig mConfig; + + static int MODE_CHANGED = 1; + static int DISPLAY_CHANGED = 2; + static int TWILIGHT_CHANGED = 4; + static int ALL_CHANGED = 255; + + // PowerManager ServiceType to use when we're only + // interested in gleaning global battery saver state. + private static final int SERVICE_TYPE_DUMMY = ServiceType.LOCATION; + + static class State { + public boolean mLowPowerMode = false; + public boolean mScreenOn = false; + public int mMode = -1; + public TwilightState mTwilight = null; + + @Override + public String toString() { + return String.format(Locale.US, + "[mLowPowerMode=%b, mScreenOn=%b, mMode=%d, mTwilight=%s", + mLowPowerMode, mScreenOn, mMode, + (mTwilight == null ? "NULL" : mTwilight.toString())); + } + } + + private final State mState = new State(); + + public LiveDisplayService(Context context) { + super(context); + + mContext = context; + + mHandlerThread = new ServiceThread(TAG, + Process.THREAD_PRIORITY_DEFAULT, false /*allowIo*/); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + + mTwilightTracker = new TwilightTracker(context); + } + + @Override + public void onStart() { + publishBinderService(LineageContextConstants.LINEAGE_LIVEDISPLAY_SERVICE, mBinder); + } + + @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); + + mPAC = new PictureAdjustmentController(mContext, mHandler); + mFeatures.add(mPAC); + + // Get capabilities, throw out any unused features + final BitSet capabilities = new BitSet(); + for (Iterator it = mFeatures.iterator(); it.hasNext();) { + final LiveDisplayFeature feature = it.next(); + if (!feature.getCapabilities(capabilities)) { + it.remove(); + } + } + + // static config + int defaultMode = mContext.getResources().getInteger( + com.android.internal.R.integer.config_defaultLiveDisplayMode); + + mConfig = new LiveDisplayConfig(capabilities, defaultMode, + mCTC.getDefaultDayTemperature(), mCTC.getDefaultNightTemperature(), + mOMC.getDefaultAutoOutdoorMode(), mDHC.getDefaultAutoContrast(), + mDHC.getDefaultCABC(), mDHC.getDefaultColorEnhancement(), + mCTC.getColorTemperatureRange(), mCTC.getColorBalanceRange(), + mPAC.getHueRange(), mPAC.getSaturationRange(), + mPAC.getIntensityRange(), mPAC.getContrastRange(), + mPAC.getSaturationThresholdRange()); + + // listeners + mDisplayManager = (DisplayManager) getContext().getSystemService( + Context.DISPLAY_SERVICE); + mDisplayManager.registerDisplayListener(mDisplayListener, null); + mState.mScreenOn = mDisplayManager.getDisplay( + Display.DEFAULT_DISPLAY).getState() == Display.STATE_ON; + + PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class); + pmi.registerLowPowerModeObserver(mLowPowerModeListener); + // ServiceType does not matter when retrieving global saver mode. + mState.mLowPowerMode = + pmi.getLowPowerState(SERVICE_TYPE_DUMMY).globalBatterySaverEnabled; + + mTwilightTracker.registerListener(mTwilightListener, mHandler); + mState.mTwilight = mTwilightTracker.getCurrentState(); + + if (mConfig.hasModeSupport()) { + mModeObserver = new ModeObserver(mHandler); + mState.mMode = mModeObserver.getMode(); + } + + // start and update all features + for (int i = 0; i < mFeatures.size(); i++) { + mFeatures.get(i).start(); + } + + updateFeatures(ALL_CHANGED); + } + } + + private void updateFeatures(final int flags) { + mHandler.post(new Runnable() { + @Override + public void run() { + for (int i = 0; i < mFeatures.size(); i++) { + mFeatures.get(i).update(flags, mState); + } + } + }); + } + + private final IBinder mBinder = new ILiveDisplayService.Stub() { + + @Override + public LiveDisplayConfig getConfig() { + return mConfig; + } + + @Override + public int getMode() { + if (mConfig != null && mConfig.hasModeSupport()) { + return mModeObserver.getMode(); + } else { + return MODE_OFF; + } + } + + @Override + public boolean setMode(int mode) { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.MANAGE_LIVEDISPLAY", null); + if (!mConfig.hasModeSupport()) { + return false; + } + return mModeObserver.setMode(mode); + } + + @Override + public float[] getColorAdjustment() { + return mDHC.getColorAdjustment(); + } + + @Override + public boolean setColorAdjustment(float[] adj) { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.MANAGE_LIVEDISPLAY", null); + return mDHC.setColorAdjustment(adj); + } + + @Override + public boolean isAutoContrastEnabled() { + return mDHC.isAutoContrastEnabled(); + } + + @Override + public boolean setAutoContrastEnabled(boolean enabled) { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.MANAGE_LIVEDISPLAY", null); + return mDHC.setAutoContrastEnabled(enabled); + } + + @Override + public boolean isCABCEnabled() { + return mDHC.isCABCEnabled(); + } + + @Override + public boolean setCABCEnabled(boolean enabled) { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.MANAGE_LIVEDISPLAY", null); + return mDHC.setCABCEnabled(enabled); + } + + @Override + public boolean isColorEnhancementEnabled() { + return mDHC.isColorEnhancementEnabled(); + } + + @Override + public boolean setColorEnhancementEnabled(boolean enabled) { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.MANAGE_LIVEDISPLAY", null); + return mDHC.setColorEnhancementEnabled(enabled); + } + + @Override + public boolean isAutomaticOutdoorModeEnabled() { + return mOMC.isAutomaticOutdoorModeEnabled(); + } + + @Override + public boolean setAutomaticOutdoorModeEnabled(boolean enabled) { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.MANAGE_LIVEDISPLAY", null); + return mOMC.setAutomaticOutdoorModeEnabled(enabled); + } + + @Override + public int getDayColorTemperature() { + return mCTC.getDayColorTemperature(); + } + + @Override + public boolean setDayColorTemperature(int temperature) { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.MANAGE_LIVEDISPLAY", null); + mCTC.setDayColorTemperature(temperature); + return true; + } + + @Override + public int getNightColorTemperature() { + return mCTC.getNightColorTemperature(); + } + + @Override + public boolean setNightColorTemperature(int temperature) { + mContext.enforceCallingOrSelfPermission( + "lineageos.permission.MANAGE_LIVEDISPLAY", null); + mCTC.setNightColorTemperature(temperature); + return true; + } + + @Override + public int getColorTemperature() { + return mCTC.getColorTemperature(); + } + + @Override + public HSIC getPictureAdjustment() { return mPAC.getPictureAdjustment(); } + + @Override + public boolean setPictureAdjustment(final HSIC hsic) { return mPAC.setPictureAdjustment(hsic); } + + @Override + public HSIC getDefaultPictureAdjustment() { return mPAC.getDefaultPictureAdjustment(); } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); + + pw.println(); + pw.println("LiveDisplay Service State:"); + pw.println(" mState=" + mState.toString()); + pw.println(" mConfig=" + mConfig.toString()); + pw.println(" mAwaitingNudge=" + mAwaitingNudge); + + for (int i = 0; i < mFeatures.size(); i++) { + mFeatures.get(i).dump(pw); + } + } + + @Override + public boolean isNight() { + final TwilightState twilight = mTwilightTracker.getCurrentState(); + return twilight != null && twilight.isNight(); + } + }; + + // 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) { + boolean screenOn = isScreenOn(); + if (screenOn != mState.mScreenOn) { + mState.mScreenOn = screenOn; + updateFeatures(DISPLAY_CHANGED); + } + } + } + }; + + + // Display postprocessing can have power impact. + private PowerManagerInternal.LowPowerModeListener mLowPowerModeListener = + new PowerManagerInternal.LowPowerModeListener() { + @Override + public void onLowPowerModeChanged(PowerSaveState state) { + final boolean lowPowerMode = state.globalBatterySaverEnabled; + if (lowPowerMode != mState.mLowPowerMode) { + mState.mLowPowerMode = lowPowerMode; + updateFeatures(MODE_CHANGED); + } + } + + @Override + public int getServiceType() { + return SERVICE_TYPE_DUMMY; + } + }; + + // Watch for mode changes + private final class ModeObserver extends UserContentObserver { + + private final Uri MODE_SETTING = + Settings.System.getUriFor(Settings.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() { + int mode = getMode(); + if (mode != mState.mMode) { + mState.mMode = mode; + + updateFeatures(MODE_CHANGED); + } + } + + int getMode() { + return getInt(Settings.System.DISPLAY_TEMPERATURE_MODE, + mConfig.getDefaultMode()); + } + + boolean setMode(int mode) { + if (mConfig.hasFeature(mode) && mode >= MODE_FIRST && mode <= MODE_LAST) { + putInt(Settings.System.DISPLAY_TEMPERATURE_MODE, mode); + if (mode != mConfig.getDefaultMode()) { + stopNudgingMe(); + } + return true; + } + return false; + } + } + + // Night watchman + private final TwilightListener mTwilightListener = new TwilightListener() { + @Override + public void onTwilightStateChanged() { + mState.mTwilight = mTwilightTracker.getCurrentState(); + updateFeatures(TWILIGHT_CHANGED); + nudge(); + } + }; + + private boolean isScreenOn() { + return mDisplayManager.getDisplay( + Display.DEFAULT_DISPLAY).getState() == Display.STATE_ON; + } + + 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 Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.LIVE_DISPLAY_HINTED, + -3, + UserHandle.USER_CURRENT); + } + + + private void updateSunsetCounter(int count) { + Settings.System.putIntForUser(mContext.getContentResolver(), + Settings.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 = mTwilightTracker.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("com.android.settings.LIVEDISPLAY_SETTINGS"); + PendingIntent result = PendingIntent.getActivity( + mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + Notification.Builder builder = new Notification.Builder(mContext) + .setContentTitle(mContext.getResources().getString( + com.android.internal.R.string.live_display_title)) + .setContentText(mContext.getResources().getString( + com.android.internal.R.string.live_display_hint)) + .setSmallIcon(com.android.internal.R.drawable.ic_livedisplay_notif) + .setStyle(new Notification.BigTextStyle().bigText(mContext.getResources() + .getString( + com.android.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 Settings.System.getIntForUser(mContext.getContentResolver(), + setting, defValue, UserHandle.USER_CURRENT); + } + + private void putInt(String setting, int value) { + Settings.System.putIntForUser(mContext.getContentResolver(), + setting, value, UserHandle.USER_CURRENT); + } +} diff --git a/services/core/java/com/android/server/custom/display/OutdoorModeController.java b/services/core/java/com/android/server/custom/display/OutdoorModeController.java new file mode 100644 index 000000000000..ccffb5384bbc --- /dev/null +++ b/services/core/java/com/android/server/custom/display/OutdoorModeController.java @@ -0,0 +1,258 @@ +/* + * 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 com.android.server.custom.display; + +import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_AUTO; +import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_DAY; +import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_OUTDOOR; + +import android.content.Context; +import android.net.Uri; +import android.os.Handler; + +import java.io.PrintWriter; +import java.util.BitSet; + +import com.android.internal.custom.hardware.LineageHardwareManager; +import com.android.internal.custom.hardware.LiveDisplayManager; +import android.provider.Settings; + +public class OutdoorModeController extends LiveDisplayFeature { + + private final LineageHardwareManager mHardware; + private AmbientLuxObserver mLuxObserver; + + // hardware capabilities + private final boolean mUseOutdoorMode; + + // default values + private final int mDefaultOutdoorLux; + private final int mOutdoorLuxHysteresis; + private final boolean mDefaultAutoOutdoorMode; + private final boolean mSelfManaged; + + // 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); + + mHardware = LineageHardwareManager.getInstance(mContext); + mUseOutdoorMode = mHardware.isSupported(LineageHardwareManager.FEATURE_SUNLIGHT_ENHANCEMENT); + mSelfManaged = mUseOutdoorMode && mHardware.isSunlightEnhancementSelfManaged(); + + mDefaultOutdoorLux = mContext.getResources().getInteger( + com.android.internal.R.integer.config_outdoorAmbientLux); + mOutdoorLuxHysteresis = mContext.getResources().getInteger( + com.android.internal.R.integer.config_outdoorAmbientLuxHysteresis); + mDefaultAutoOutdoorMode = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_defaultAutoOutdoorMode); + } + + @Override + public void onStart() { + if (!mUseOutdoorMode) { + return; + } + + if (!mSelfManaged) { + mLuxObserver = new AmbientLuxObserver(mContext, mHandler.getLooper(), + mDefaultOutdoorLux, mOutdoorLuxHysteresis, SENSOR_WINDOW_MS); + } + + registerSettings( + Settings.System.getUriFor(Settings.System.DISPLAY_AUTO_OUTDOOR_MODE)); + } + + @Override + public boolean getCapabilities(final BitSet caps) { + if (mUseOutdoorMode) { + caps.set(LiveDisplayManager.MODE_OUTDOOR); + if (mSelfManaged) { + caps.set(LiveDisplayManager.FEATURE_MANAGED_OUTDOOR_MODE); + } + } + return mUseOutdoorMode; + } + + @Override + protected void onUpdate() { + updateOutdoorMode(); + } + + @Override + protected void onTwilightUpdated() { + updateOutdoorMode(); + } + + @Override + protected synchronized void onScreenStateChanged() { + if (!mUseOutdoorMode) { + return; + } + + // toggle the sensor when screen on/off + updateSensorState(); + + // Disable outdoor mode on screen off so that we don't melt the users + // face if they turn it back on in normal conditions + if (!isScreenOn() && !mSelfManaged && getMode() != MODE_OUTDOOR) { + mIsOutdoor = false; + mHardware.set(LineageHardwareManager.FEATURE_SUNLIGHT_ENHANCEMENT, false); + } + } + + @Override + public synchronized void onSettingsChanged(Uri uri) { + 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(" mOutdoorLuxHysteresis=" + mOutdoorLuxHysteresis); + pw.println(); + pw.println(" OutdoorModeController State:"); + pw.println(" mAutoOutdoorMode=" + isAutomaticOutdoorModeEnabled()); + pw.println(" mIsOutdoor=" + mIsOutdoor); + pw.println(" mIsNight=" + isNight()); + pw.println(" hardware state=" + + mHardware.get(LineageHardwareManager.FEATURE_SUNLIGHT_ENHANCEMENT)); + } + mLuxObserver.dump(pw); + } + + private synchronized void updateSensorState() { + if (!mUseOutdoorMode || mLuxObserver == null || mSelfManaged) { + return; + } + + /* + * Light sensor: + */ + boolean sensorEnabled = false; + // no sensor if low power mode or when the screen is off + if (isScreenOn() && !isLowPowerMode()) { + if (isAutomaticOutdoorModeEnabled()) { + int mode = getMode(); + if (mode == MODE_DAY) { + // always turn it on if day mode is selected + sensorEnabled = true; + } else if (mode == MODE_AUTO && !isNight()) { + // in auto mode we turn it on during actual daytime + sensorEnabled = true; + } + } + } + if (mIsSensorEnabled != sensorEnabled) { + mIsSensorEnabled = sensorEnabled; + mLuxObserver.setTransitionListener(sensorEnabled ? 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() { + if (!mUseOutdoorMode) { + return; + } + + updateSensorState(); + + /* + * Should we turn on outdoor mode or not? + * + * Do nothing if the screen is off. + */ + if (isScreenOn()) { + boolean enabled = false; + // turn it off in low power mode + if (!isLowPowerMode()) { + int mode = getMode(); + // turn it on if the user manually selected the mode + if (mode == MODE_OUTDOOR) { + enabled = true; + } else if (isAutomaticOutdoorModeEnabled()) { + // self-managed mode means we just flip a switch and an external + // implementation does all the sensing. this allows the user + // to turn on/off the feature. + if (mSelfManaged) { + enabled = true; + } else if (mIsOutdoor) { + // if we're here, the sensor detects extremely bright light. + if (mode == MODE_DAY) { + // if the user manually selected day mode, go ahead and + // melt their face + enabled = true; + } else if (mode == MODE_AUTO && !isNight()) { + // if we're in auto mode, we should also check if it's + // night time, since we don't get much sun at night + // on this planet :) + enabled = true; + } + } + } + } + mHardware.set(LineageHardwareManager.FEATURE_SUNLIGHT_ENHANCEMENT, enabled); + } + } + + 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(); + } + } + }; + + boolean setAutomaticOutdoorModeEnabled(boolean enabled) { + if (!mUseOutdoorMode) { + return false; + } + putBoolean(Settings.System.DISPLAY_AUTO_OUTDOOR_MODE, enabled); + return true; + } + + boolean isAutomaticOutdoorModeEnabled() { + return mUseOutdoorMode && + getBoolean(Settings.System.DISPLAY_AUTO_OUTDOOR_MODE, + getDefaultAutoOutdoorMode()); + } + + boolean getDefaultAutoOutdoorMode() { + return mDefaultAutoOutdoorMode; + } +} diff --git a/services/core/java/com/android/server/custom/display/PictureAdjustmentController.java b/services/core/java/com/android/server/custom/display/PictureAdjustmentController.java new file mode 100644 index 000000000000..60130ef76303 --- /dev/null +++ b/services/core/java/com/android/server/custom/display/PictureAdjustmentController.java @@ -0,0 +1,242 @@ +/* + * 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 com.android.server.custom.display; + +import android.content.Context; +import android.net.Uri; +import android.os.Handler; +import android.text.TextUtils; +import android.util.Range; +import android.util.Slog; +import android.util.SparseArray; +import android.view.Display; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.List; + +import com.android.internal.custom.hardware.LineageHardwareManager; +import com.android.internal.custom.hardware.DisplayMode; +import com.android.internal.custom.hardware.HSIC; +import com.android.internal.custom.hardware.LiveDisplayManager; +import android.provider.Settings; + +public class PictureAdjustmentController extends LiveDisplayFeature { + + private static final String TAG = "LiveDisplay-PAC"; + + private final LineageHardwareManager mHardware; + private final boolean mUsePictureAdjustment; + private final boolean mHasDisplayModes; + + private List> mRanges = new ArrayList>(); + + public PictureAdjustmentController(Context context, Handler handler) { + super(context, handler); + mHardware = LineageHardwareManager.getInstance(context); + mHasDisplayModes = mHardware.isSupported(LineageHardwareManager.FEATURE_DISPLAY_MODES); + + boolean usePA = mHardware.isSupported(LineageHardwareManager.FEATURE_PICTURE_ADJUSTMENT); + if (usePA) { + mRanges.addAll(mHardware.getPictureAdjustmentRanges()); + if (mRanges.size() < 4) { + usePA = false; + } else { + for (Range range : mRanges) { + if (range.getLower() == 0.0f && range.getUpper() == 0.0f) { + usePA = false; + break; + } + } + } + } + if (!usePA) { + mRanges.clear(); + } + mUsePictureAdjustment = usePA; + } + + @Override + public void onStart() { + if (!mUsePictureAdjustment) { + return; + } + + registerSettings( + Settings.System.getUriFor(Settings.System.DISPLAY_PICTURE_ADJUSTMENT)); + } + + @Override + protected void onSettingsChanged(Uri uri) {// nothing to do for mode switch + updatePictureAdjustment(); + } + + @Override + protected void onUpdate() { + updatePictureAdjustment(); + } + + private void updatePictureAdjustment() { + if (mUsePictureAdjustment && isScreenOn()) { + final HSIC hsic = getPictureAdjustment(); + if (hsic != null) { + if (!mHardware.setPictureAdjustment(hsic)) { + Slog.e(TAG, "Failed to set picture adjustment! " + hsic.toString()); + } + } + } + } + + @Override + public void dump(PrintWriter pw) { + if (mUsePictureAdjustment) { + pw.println(); + pw.println("PictureAdjustmentController Configuration:"); + pw.println(" adjustment=" + getPictureAdjustment()); + pw.println(" hueRange=" + getHueRange()); + pw.println(" saturationRange=" + getSaturationRange()); + pw.println(" intensityRange=" + getIntensityRange()); + pw.println(" contrastRange=" + getContrastRange()); + pw.println(" saturationThresholdRange=" + getSaturationThresholdRange()); + pw.println(" defaultAdjustment=" + getDefaultPictureAdjustment()); + } + } + + + @Override + public boolean getCapabilities(BitSet caps) { + if (mUsePictureAdjustment) { + caps.set(LiveDisplayManager.FEATURE_PICTURE_ADJUSTMENT); + } + return mUsePictureAdjustment; + } + + Range getHueRange() { + return mUsePictureAdjustment && mRanges.size() > 0 + ? mRanges.get(0) : Range.create(0.0f, 0.0f); + } + + Range getSaturationRange() { + return mUsePictureAdjustment && mRanges.size() > 1 + ? mRanges.get(1) : Range.create(0.0f, 0.0f); + } + + Range getIntensityRange() { + return mUsePictureAdjustment && mRanges.size() > 2 + ? mRanges.get(2) : Range.create(0.0f, 0.0f); + } + + Range getContrastRange() { + return mUsePictureAdjustment && mRanges.size() > 3 ? + mRanges.get(3) : Range.create(0.0f, 0.0f); + } + + Range getSaturationThresholdRange() { + return mUsePictureAdjustment && mRanges.size() > 4 ? + mRanges.get(4) : Range.create(0.0f, 0.0f); + } + + HSIC getDefaultPictureAdjustment() { + HSIC hsic = null; + if (mUsePictureAdjustment) { + hsic = mHardware.getDefaultPictureAdjustment(); + } + if (hsic == null) { + hsic = new HSIC(0.0f, 0.0f, 0.0f, 0.0f, 0.0f); + } + return hsic; + } + + HSIC getPictureAdjustment() { + HSIC hsic = null; + if (mUsePictureAdjustment) { + int modeID = 0; + if (mHasDisplayModes) { + DisplayMode mode = mHardware.getCurrentDisplayMode(); + if (mode != null) { + modeID = mode.id; + } + } + hsic = getPAForMode(modeID); + } + if (hsic == null) { + hsic = new HSIC(0.0f, 0.0f, 0.0f, 0.0f, 0.0f); + } + return hsic; + } + + boolean setPictureAdjustment(HSIC hsic) { + if (mUsePictureAdjustment && hsic != null) { + int modeID = 0; + if (mHasDisplayModes) { + DisplayMode mode = mHardware.getCurrentDisplayMode(); + if (mode != null) { + modeID = mode.id; + } + } + setPAForMode(modeID, hsic); + return true; + } + return false; + } + + // TODO: Expose mode-based settings to upper layers + + private HSIC getPAForMode(int mode) { + final SparseArray prefs = unpackPreference(); + if (prefs.indexOfKey(mode) >= 0) { + return prefs.get(mode); + } + return getDefaultPictureAdjustment(); + } + + private void setPAForMode(int mode, HSIC hsic) { + final SparseArray prefs = unpackPreference(); + prefs.put(mode, hsic); + packPreference(prefs); + } + + private SparseArray unpackPreference() { + final SparseArray ret = new SparseArray(); + + String pref = getString(Settings.System.DISPLAY_PICTURE_ADJUSTMENT); + if (pref != null) { + String[] byMode = TextUtils.split(pref, ","); + for (String mode : byMode) { + String[] modePA = TextUtils.split(mode, ":"); + if (modePA.length == 2) { + ret.put(Integer.valueOf(modePA[0]), HSIC.unflattenFrom(modePA[1])); + } + } + } + return ret; + } + + private void packPreference(final SparseArray modes) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < modes.size(); i++) { + int id = modes.keyAt(i); + HSIC m = modes.get(id); + if (i > 0) { + sb.append(","); + } + sb.append(id).append(":").append(m.flatten()); + } + putString(Settings.System.DISPLAY_PICTURE_ADJUSTMENT, sb.toString()); + } + +} diff --git a/services/core/java/com/android/server/custom/display/TwilightCalculator.java b/services/core/java/com/android/server/custom/display/TwilightCalculator.java new file mode 100644 index 000000000000..4c73152d1caf --- /dev/null +++ b/services/core/java/com/android/server/custom/display/TwilightCalculator.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.custom.display; + +import android.text.format.DateUtils; +import android.util.FloatMath; + +/** @hide */ +public class TwilightCalculator { + + /** Value of {@link #mState} if it is currently day */ + public static final int DAY = 0; + + /** Value of {@link #mState} if it is currently night */ + public static final int NIGHT = 1; + + private static final float DEGREES_TO_RADIANS = (float) (Math.PI / 180.0f); + + // element for calculating solar transit. + private static final float J0 = 0.0009f; + + // correction for civil twilight + private static final float ALTIDUTE_CORRECTION_CIVIL_TWILIGHT = -0.104719755f; + + // coefficients for calculating Equation of Center. + private static final float C1 = 0.0334196f; + private static final float C2 = 0.000349066f; + private static final float C3 = 0.000005236f; + + private static final float OBLIQUITY = 0.40927971f; + + // Java time on Jan 1, 2000 12:00 UTC. + private static final long UTC_2000 = 946728000000L; + + /** + * Time of sunset (civil twilight) in milliseconds or -1 in the case the day + * or night never ends. + */ + public long mSunset; + + /** + * Time of sunrise (civil twilight) in milliseconds or -1 in the case the + * day or night never ends. + */ + public long mSunrise; + + /** Current state */ + public int mState; + + /** + * calculates the civil twilight bases on time and geo-coordinates. + * + * @param time time in milliseconds. + * @param latiude latitude in degrees. + * @param longitude latitude in degrees. + */ + public void calculateTwilight(long time, double latiude, double longitude) { + final float daysSince2000 = (float) (time - UTC_2000) / DateUtils.DAY_IN_MILLIS; + + // mean anomaly + final float meanAnomaly = 6.240059968f + daysSince2000 * 0.01720197f; + + // true anomaly + final float trueAnomaly = meanAnomaly + C1 * FloatMath.sin(meanAnomaly) + C2 + * FloatMath.sin(2 * meanAnomaly) + C3 * FloatMath.sin(3 * meanAnomaly); + + // ecliptic longitude + final float solarLng = trueAnomaly + 1.796593063f + (float) Math.PI; + + // solar transit in days since 2000 + final double arcLongitude = -longitude / 360; + float n = Math.round(daysSince2000 - J0 - arcLongitude); + double solarTransitJ2000 = n + J0 + arcLongitude + 0.0053f * FloatMath.sin(meanAnomaly) + + -0.0069f * FloatMath.sin(2 * solarLng); + + // declination of sun + double solarDec = Math.asin(FloatMath.sin(solarLng) * FloatMath.sin(OBLIQUITY)); + + final double latRad = latiude * DEGREES_TO_RADIANS; + + double cosHourAngle = (FloatMath.sin(ALTIDUTE_CORRECTION_CIVIL_TWILIGHT) - Math.sin(latRad) + * Math.sin(solarDec)) / (Math.cos(latRad) * Math.cos(solarDec)); + // The day or night never ends for the given date and location, if this value is out of + // range. + if (cosHourAngle >= 1) { + mState = NIGHT; + mSunset = -1; + mSunrise = -1; + return; + } else if (cosHourAngle <= -1) { + mState = DAY; + mSunset = -1; + mSunrise = -1; + return; + } + + float hourAngle = (float) (Math.acos(cosHourAngle) / (2 * Math.PI)); + + mSunset = Math.round((solarTransitJ2000 + hourAngle) * DateUtils.DAY_IN_MILLIS) + UTC_2000; + mSunrise = Math.round((solarTransitJ2000 - hourAngle) * DateUtils.DAY_IN_MILLIS) + UTC_2000; + + if (mSunrise < time && mSunset > time) { + mState = DAY; + } else { + mState = NIGHT; + } + } + +} diff --git a/services/core/java/com/android/server/custom/display/TwilightTracker.java b/services/core/java/com/android/server/custom/display/TwilightTracker.java new file mode 100644 index 000000000000..28c8abb37161 --- /dev/null +++ b/services/core/java/com/android/server/custom/display/TwilightTracker.java @@ -0,0 +1,561 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.custom.display; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.location.Criteria; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.text.format.DateUtils; +import android.text.format.Time; +import android.util.Slog; + +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.Objects; + +/** + * Figures out whether it's twilight time based on the user's location. + * + * Used by the UI mode manager and other components to adjust night mode + * effects based on sunrise and sunset. + */ +public final class TwilightTracker { + private static final String TAG = "TwilightTracker"; + private static final boolean DEBUG = false; + private static final String ACTION_UPDATE_TWILIGHT_STATE = + "lineageos.platform.intent.action.UPDATE_TWILIGHT_STATE"; + + private final Object mLock = new Object(); + + private final AlarmManager mAlarmManager; + private final LocationManager mLocationManager; + private final LocationHandler mLocationHandler; + + private final ArrayList mListeners = + new ArrayList(); + + private TwilightState mTwilightState; + + private final Context mContext; + + public TwilightTracker(Context context) { + mContext = context; + mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + mLocationManager = (LocationManager) mContext.getSystemService( + Context.LOCATION_SERVICE); + mLocationHandler = new LocationHandler(); + + IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED); + filter.addAction(Intent.ACTION_TIME_CHANGED); + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + filter.addAction(ACTION_UPDATE_TWILIGHT_STATE); + mContext.registerReceiver(mUpdateLocationReceiver, filter); + } + + /** + * Gets the current twilight state. + * + * @return The current twilight state, or null if no information is available. + */ + public TwilightState getCurrentState() { + synchronized (mLock) { + return mTwilightState; + } + } + + /** + * Listens for twilight time. + * + * @param listener The listener. + */ + public void registerListener(TwilightListener listener, Handler handler) { + synchronized (mLock) { + mListeners.add(new TwilightListenerRecord(listener, handler)); + + if (mListeners.size() == 1) { + mLocationHandler.enableLocationUpdates(); + } + } + } + + + private void setTwilightState(TwilightState state) { + synchronized (mLock) { + if (!Objects.equals(mTwilightState, state)) { + if (DEBUG) { + Slog.d(TAG, "Twilight state changed: " + state); + } + + mTwilightState = state; + + final int listenerLen = mListeners.size(); + for (int i = 0; i < listenerLen; i++) { + mListeners.get(i).postUpdate(); + } + } + } + } + + // The user has moved if the accuracy circles of the two locations don't overlap. + private static boolean hasMoved(Location from, Location to) { + if (to == null) { + return false; + } + + if (from == null) { + return true; + } + + // if new location is older than the current one, the device hasn't moved. + if (to.getElapsedRealtimeNanos() < from.getElapsedRealtimeNanos()) { + return false; + } + + // Get the distance between the two points. + float distance = from.distanceTo(to); + + // Get the total accuracy radius for both locations. + float totalAccuracy = from.getAccuracy() + to.getAccuracy(); + + // If the distance is greater than the combined accuracy of the two + // points then they can't overlap and hence the user has moved. + return distance >= totalAccuracy; + } + + private final class LocationHandler extends Handler { + private static final int MSG_ENABLE_LOCATION_UPDATES = 1; + private static final int MSG_GET_NEW_LOCATION_UPDATE = 2; + private static final int MSG_PROCESS_NEW_LOCATION = 3; + private static final int MSG_DO_TWILIGHT_UPDATE = 4; + + private static final long LOCATION_UPDATE_MS = 24 * DateUtils.HOUR_IN_MILLIS; + private static final long MIN_LOCATION_UPDATE_MS = 30 * DateUtils.MINUTE_IN_MILLIS; + private static final float LOCATION_UPDATE_DISTANCE_METER = 1000 * 20; + private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MIN = 5000; + private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MAX = + 15 * DateUtils.MINUTE_IN_MILLIS; + private static final double FACTOR_GMT_OFFSET_LONGITUDE = + 1000.0 * 360.0 / DateUtils.DAY_IN_MILLIS; + + private boolean mPassiveListenerEnabled; + private boolean mNetworkListenerEnabled; + private boolean mDidFirstInit; + private long mLastNetworkRegisterTime = -MIN_LOCATION_UPDATE_MS; + private long mLastUpdateInterval; + private Location mLocation; + private final TwilightCalculator mTwilightCalculator = new TwilightCalculator(); + + public void processNewLocation(Location location) { + Message msg = obtainMessage(MSG_PROCESS_NEW_LOCATION, location); + sendMessage(msg); + } + + public void enableLocationUpdates() { + sendEmptyMessage(MSG_ENABLE_LOCATION_UPDATES); + } + + public void requestLocationUpdate() { + sendEmptyMessage(MSG_GET_NEW_LOCATION_UPDATE); + } + + public void requestTwilightUpdate() { + sendEmptyMessage(MSG_DO_TWILIGHT_UPDATE); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_PROCESS_NEW_LOCATION: { + final Location location = (Location) msg.obj; + final boolean hasMoved = hasMoved(mLocation, location); + final boolean hasBetterAccuracy = mLocation == null + || location.getAccuracy() < mLocation.getAccuracy(); + if (DEBUG) { + Slog.d(TAG, "Processing new location: " + location + + ", hasMoved=" + hasMoved + + ", hasBetterAccuracy=" + hasBetterAccuracy); + } + if (hasMoved || hasBetterAccuracy) { + setLocation(location); + } + break; + } + + case MSG_GET_NEW_LOCATION_UPDATE: + if (!mNetworkListenerEnabled) { + // Don't do anything -- we are still trying to get a + // location. + return; + } + if ((mLastNetworkRegisterTime + MIN_LOCATION_UPDATE_MS) >= + SystemClock.elapsedRealtime()) { + // Don't do anything -- it hasn't been long enough + // since we last requested an update. + return; + } + + // Unregister the current location monitor, so we can + // register a new one for it to get an immediate update. + mNetworkListenerEnabled = false; + mLocationManager.removeUpdates(mEmptyLocationListener); + + // Fall through to re-register listener. + case MSG_ENABLE_LOCATION_UPDATES: + // enable network provider to receive at least location updates for a given + // distance. + boolean networkLocationEnabled; + try { + networkLocationEnabled = + mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); + } catch (Exception e) { + // we may get IllegalArgumentException if network location provider + // does not exist or is not yet installed. + networkLocationEnabled = false; + } + if (!mNetworkListenerEnabled && networkLocationEnabled) { + mNetworkListenerEnabled = true; + mLastNetworkRegisterTime = SystemClock.elapsedRealtime(); + mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, + LOCATION_UPDATE_MS, 0, mEmptyLocationListener); + + if (!mDidFirstInit) { + mDidFirstInit = true; + if (mLocation == null) { + retrieveLocation(); + } + } + } + + // enable passive provider to receive updates from location fixes (gps + // and network). + boolean passiveLocationEnabled; + try { + passiveLocationEnabled = + mLocationManager.isProviderEnabled(LocationManager.PASSIVE_PROVIDER); + } catch (Exception e) { + // we may get IllegalArgumentException if passive location provider + // does not exist or is not yet installed. + passiveLocationEnabled = false; + } + + if (!mPassiveListenerEnabled && passiveLocationEnabled) { + mPassiveListenerEnabled = true; + mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, + 0, LOCATION_UPDATE_DISTANCE_METER, mLocationListener); + } + + if (!(mNetworkListenerEnabled && mPassiveListenerEnabled)) { + mLastUpdateInterval *= 1.5; + if (mLastUpdateInterval == 0) { + mLastUpdateInterval = LOCATION_UPDATE_ENABLE_INTERVAL_MIN; + } else if (mLastUpdateInterval > LOCATION_UPDATE_ENABLE_INTERVAL_MAX) { + mLastUpdateInterval = LOCATION_UPDATE_ENABLE_INTERVAL_MAX; + } + sendEmptyMessageDelayed(MSG_ENABLE_LOCATION_UPDATES, mLastUpdateInterval); + } + + if (!networkLocationEnabled && mLocation == null) { + if (DEBUG) { + Slog.d(TAG, "Network location unavailable"); + } + retrieveLocation(); + } + break; + + case MSG_DO_TWILIGHT_UPDATE: + updateTwilightState(); + break; + } + } + + private void retrieveLocation() { + Location location = null; + final Iterator providers = + mLocationManager.getProviders(new Criteria(), true).iterator(); + while (providers.hasNext()) { + final Location lastKnownLocation = + mLocationManager.getLastKnownLocation(providers.next()); + // pick the most recent location + if (location == null || (lastKnownLocation != null && + location.getElapsedRealtimeNanos() < + lastKnownLocation.getElapsedRealtimeNanos())) { + location = lastKnownLocation; + } + } + + // In the case there is no location available (e.g. GPS fix or network location + // is not available yet), the longitude of the location is estimated using the timezone, + // latitude and accuracy are set to get a good average. + if (location == null) { + Time currentTime = new Time(); + currentTime.set(System.currentTimeMillis()); + double lngOffset = FACTOR_GMT_OFFSET_LONGITUDE * + (currentTime.gmtoff - (currentTime.isDst > 0 ? 3600 : 0)); + location = new Location("fake"); + location.setLongitude(lngOffset); + location.setLatitude(0); + location.setAccuracy(417000.0f); + location.setTime(System.currentTimeMillis()); + location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()); + + if (DEBUG) { + Slog.d(TAG, "Estimated location from timezone: " + location); + } + } + + setLocation(location); + } + + private void setLocation(Location location) { + mLocation = location; + updateTwilightState(); + } + + private void updateTwilightState() { + if (mLocation == null) { + setTwilightState(null); + return; + } + + final long now = System.currentTimeMillis(); + + // calculate yesterday's twilight + mTwilightCalculator.calculateTwilight(now - DateUtils.DAY_IN_MILLIS, + mLocation.getLatitude(), mLocation.getLongitude()); + final long yesterdaySunset = mTwilightCalculator.mSunset; + + // calculate today's twilight + mTwilightCalculator.calculateTwilight(now, + mLocation.getLatitude(), mLocation.getLongitude()); + final boolean isNight = (mTwilightCalculator.mState == TwilightCalculator.NIGHT); + final long todaySunrise = mTwilightCalculator.mSunrise; + final long todaySunset = mTwilightCalculator.mSunset; + + // calculate tomorrow's twilight + mTwilightCalculator.calculateTwilight(now + DateUtils.DAY_IN_MILLIS, + mLocation.getLatitude(), mLocation.getLongitude()); + final long tomorrowSunrise = mTwilightCalculator.mSunrise; + + // set twilight state + TwilightState state = new TwilightState(isNight, yesterdaySunset, + todaySunrise, todaySunset, tomorrowSunrise); + if (DEBUG) { + Slog.d(TAG, "Updating twilight state: " + state); + } + setTwilightState(state); + + // schedule next update + long nextUpdate = 0; + if (todaySunrise == -1 || todaySunset == -1) { + // In the case the day or night never ends the update is scheduled 12 hours later. + nextUpdate = now + 12 * DateUtils.HOUR_IN_MILLIS; + } else { + // add some extra time to be on the safe side. + nextUpdate += DateUtils.MINUTE_IN_MILLIS; + + if (now > todaySunset) { + nextUpdate += tomorrowSunrise; + } else if (now > todaySunrise) { + nextUpdate += todaySunset; + } else { + nextUpdate += todaySunrise; + } + } + + if (DEBUG) { + Slog.d(TAG, "Next update in " + (nextUpdate - now) + " ms"); + } + + Intent updateIntent = new Intent(ACTION_UPDATE_TWILIGHT_STATE); + PendingIntent pendingIntent = PendingIntent.getBroadcast( + mContext, 0, updateIntent, 0); + mAlarmManager.cancel(pendingIntent); + mAlarmManager.setExact(AlarmManager.RTC, nextUpdate, pendingIntent); + } + } + + private final BroadcastReceiver mUpdateLocationReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(intent.getAction()) + && !intent.getBooleanExtra("state", false)) { + // Airplane mode is now off! + mLocationHandler.requestLocationUpdate(); + return; + } + + // Time zone has changed or alarm expired. + mLocationHandler.requestTwilightUpdate(); + } + }; + + // A LocationListener to initialize the network location provider. The location updates + // are handled through the passive location provider. + private final LocationListener mEmptyLocationListener = new LocationListener() { + public void onLocationChanged(Location location) { + } + + public void onProviderDisabled(String provider) { + } + + public void onProviderEnabled(String provider) { + } + + public void onStatusChanged(String provider, int status, Bundle extras) { + } + }; + + private final LocationListener mLocationListener = new LocationListener() { + public void onLocationChanged(Location location) { + mLocationHandler.processNewLocation(location); + } + + public void onProviderDisabled(String provider) { + } + + public void onProviderEnabled(String provider) { + } + + public void onStatusChanged(String provider, int status, Bundle extras) { + } + }; + + public static class TwilightState { + private final boolean mIsNight; + private final long mYesterdaySunset; + private final long mTodaySunrise; + private final long mTodaySunset; + private final long mTomorrowSunrise; + + TwilightState(boolean isNight, + long yesterdaySunset, + long todaySunrise, long todaySunset, + long tomorrowSunrise) { + mIsNight = isNight; + mYesterdaySunset = yesterdaySunset; + mTodaySunrise = todaySunrise; + mTodaySunset = todaySunset; + mTomorrowSunrise = tomorrowSunrise; + } + + /** + * Returns true if it is currently night time. + */ + public boolean isNight() { + return mIsNight; + } + + /** + * Returns the time of yesterday's sunset in the System.currentTimeMillis() timebase, + * or -1 if the sun never sets. + */ + public long getYesterdaySunset() { + return mYesterdaySunset; + } + + /** + * Returns the time of today's sunrise in the System.currentTimeMillis() timebase, + * or -1 if the sun never rises. + */ + public long getTodaySunrise() { + return mTodaySunrise; + } + + /** + * Returns the time of today's sunset in the System.currentTimeMillis() timebase, + * or -1 if the sun never sets. + */ + public long getTodaySunset() { + return mTodaySunset; + } + + /** + * Returns the time of tomorrow's sunrise in the System.currentTimeMillis() timebase, + * or -1 if the sun never rises. + */ + public long getTomorrowSunrise() { + return mTomorrowSunrise; + } + + @Override + public boolean equals(Object o) { + return o instanceof TwilightState && equals((TwilightState) o); + } + + public boolean equals(TwilightState other) { + return other != null + && mIsNight == other.mIsNight + && mYesterdaySunset == other.mYesterdaySunset + && mTodaySunrise == other.mTodaySunrise + && mTodaySunset == other.mTodaySunset + && mTomorrowSunrise == other.mTomorrowSunrise; + } + + @Override + public int hashCode() { + return 0; // don't care + } + + @Override + public String toString() { + DateFormat f = DateFormat.getDateTimeInstance(); + return "{TwilightState: isNight=" + mIsNight + + ", mYesterdaySunset=" + f.format(new Date(mYesterdaySunset)) + + ", mTodaySunrise=" + f.format(new Date(mTodaySunrise)) + + ", mTodaySunset=" + f.format(new Date(mTodaySunset)) + + ", mTomorrowSunrise=" + f.format(new Date(mTomorrowSunrise)) + + "}"; + + } + } + + public interface TwilightListener { + void onTwilightStateChanged(); + } + + private static class TwilightListenerRecord implements Runnable { + private final TwilightListener mListener; + private final Handler mHandler; + + public TwilightListenerRecord(TwilightListener listener, Handler handler) { + mListener = listener; + mHandler = handler; + } + + public void postUpdate() { + mHandler.post(this); + } + + @Override + public void run() { + mListener.onTwilightStateChanged(); + } + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index bf8e7a5621c4..4c9ff0e7f623 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -194,6 +194,11 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; + +// LiveDisplay +import com.android.server.custom.LineageHardwareService; +import com.android.server.custom.display.LiveDisplayService; + public final class SystemServer { private static final String TAG = "SystemServer"; @@ -2020,6 +2025,13 @@ private void startOtherServices(@NonNull TimingsTraceAndSlog t) { t.traceEnd(); } + // LiveDisplay + t.traceBegin("StartLineageHardwareService"); + mSystemServiceManager.startService(LineageHardwareService.class); + t.traceEnd(); + t.traceBegin("StartLiveDisplayService"); + mSystemServiceManager.startService(LiveDisplayService.class); + t.traceEnd(); } if (!isWatch) { From b38ff043bc1ca96ccc24480fa7b3ae7ab934f937 Mon Sep 17 00:00:00 2001 From: Steve Kondik Date: Sun, 5 Nov 2017 16:37:52 +0100 Subject: [PATCH 08/22] SystemUI: Add LiveDisplay tile Author: Steve Kondik Date: Sun Nov 5 16:37:52 2017 +0100 SystemUI: LiveDisplay tile Change-Id: I53d492e5cb9998268104d5750705aa5ed55d9658 Signed-off-by: Joey Rizzoli Author: Unpublished Date: Fri Dec 29 23:00:01 2017 +0100 livedisplay: Allow tile creation before boot completed phase [2/2] Change-Id: Ibef44d4e07da9baf296796515288c9d42aba8608 Author: Michael Bestas Date: Thu Feb 1 16:39:16 2018 +0000 Edit: Refactor for Oreo Change-Id: Ibef44d4e07da9baf296796515288c9d42aba8608 Author: Bruno Martins Date: Thu Feb 1 17:01:43 2018 +0000 LiveDisplayTile: Avoid NPE during boot up phase * During boot, with LineageHW not yet initialized there is no way to check the current LiveDisplay mode. In such cases, fallback to AUTO. Change-Id: Ifc004c86dfd68f73b8a362a1c833cbc3e7397811 Author: Han Wang <416810799@qq.com> Date: Tue Jun 4 18:03:59 2019 +0200 LiveDisplayTile: Report unavailable on HWC2 Change-Id: Ic10381e25046a2338bc6bda36ecbb5d5cefc1ab8 Change-Id: I188df4f401e506db355ad8bb4b5307a8d0c3c35b Signed-off-by: DennySPB Signed-off-by: DennySPb --- packages/SystemUI/AndroidManifest.xml | 3 + .../android/systemui/plugins/qs/QSTile.java | 27 ++ packages/SystemUI/res/values/config.xml | 4 +- .../systemui/qs/tileimpl/QSFactoryImpl.java | 8 +- .../systemui/qs/tiles/LiveDisplayTile.java | 230 ++++++++++++++++++ 5 files changed, 269 insertions(+), 3 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/qs/tiles/LiveDisplayTile.java diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index eeafbd284a04..0849ab29e127 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -270,6 +270,9 @@ + + + - wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast,hotspot,screenrecord,dark + wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast,livedisplay,hotspot,screenrecord,dark @@ -114,7 +114,7 @@ - wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,screenrecord,reverse,aod,nfc + wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,livedisplay,screenrecord,reverse,aod,nfc diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index 0c5fb3d87a5b..e65f411a8c4f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -37,6 +37,7 @@ import com.android.systemui.qs.tiles.DndTile; import com.android.systemui.qs.tiles.FlashlightTile; import com.android.systemui.qs.tiles.HotspotTile; +import com.android.systemui.qs.tiles.LiveDisplayTile; import com.android.systemui.qs.tiles.LocationTile; import com.android.systemui.qs.tiles.NfcTile; import com.android.systemui.qs.tiles.NightDisplayTile; @@ -80,6 +81,7 @@ public class QSFactoryImpl implements QSFactory { private final Provider mUiModeNightTileProvider; private final Provider mScreenRecordTileProvider; private final Provider mAODTileProvider; + private final Provider mLiveDisplayTileProvider; private final Lazy mQsHostLazy; @@ -105,7 +107,8 @@ public QSFactoryImpl(Lazy qsHostLazy, Provider memoryTileProvider, Provider uiModeNightTileProvider, Provider screenRecordTileProvider, - Provider aodTileProvider) { + Provider aodTileProvider, + Provider liveDisplayTileProvider) { mQsHostLazy = qsHostLazy; mWifiTileProvider = wifiTileProvider; mBluetoothTileProvider = bluetoothTileProvider; @@ -128,6 +131,7 @@ public QSFactoryImpl(Lazy qsHostLazy, mUiModeNightTileProvider = uiModeNightTileProvider; mScreenRecordTileProvider = screenRecordTileProvider; mAODTileProvider = aodTileProvider; + mLiveDisplayTileProvider = liveDisplayTileProvider; } public QSTile createTile(String tileSpec) { @@ -181,6 +185,8 @@ private QSTileImpl createTileInternal(String tileSpec) { return mScreenRecordTileProvider.get(); case "aod": return mAODTileProvider.get(); + case "livedisplay": + return mLiveDisplayTileProvider.get(); } // Custom tiles diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LiveDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LiveDisplayTile.java new file mode 100644 index 000000000000..6ae9b93062a3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LiveDisplayTile.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * Copyright (C) 2018-2019 The LineageOS Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles; + +import static com.android.internal.custom.hardware.LiveDisplayManager.FEATURE_MANAGED_OUTDOOR_MODE; +import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_AUTO; +import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_DAY; +import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_OFF; +import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_OUTDOOR; + +import android.content.Intent; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.database.ContentObserver; +import android.hardware.display.ColorDisplayManager; +import android.os.Handler; +import android.os.UserHandle; +import android.provider.Settings; +import android.service.quicksettings.Tile; + +import com.android.internal.util.ArrayUtils; +import com.android.systemui.plugins.qs.QSTile.LiveDisplayState; +import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.tileimpl.QSTileImpl; + +import com.android.internal.R; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.custom.hardware.LiveDisplayManager; + + +import javax.inject.Inject; + +/** Quick settings tile: LiveDisplay mode switcher **/ +public class LiveDisplayTile extends QSTileImpl { + + private static final Intent LIVEDISPLAY_SETTINGS = + new Intent("com.android.settings.LIVEDISPLAY_SETTINGS"); + + private final LiveDisplayObserver mObserver; + private String mTitle; + private String[] mEntries; + private String[] mDescriptionEntries; + private String[] mAnnouncementEntries; + private String[] mValues; + private final int[] mEntryIconRes; + + private boolean mListening; + + private int mDayTemperature; + + private final boolean mOutdoorModeAvailable; + + private final LiveDisplayManager mLiveDisplay; + + private static final int OFF_TEMPERATURE = 6500; + + @Inject + public LiveDisplayTile(QSHost host) { + super(host); + + Resources res = mContext.getResources(); + TypedArray typedArray = res.obtainTypedArray(R.array.live_display_drawables); + mEntryIconRes = new int[typedArray.length()]; + for (int i = 0; i < mEntryIconRes.length; i++) { + mEntryIconRes[i] = typedArray.getResourceId(i, 0); + } + typedArray.recycle(); + + updateEntries(); + + mLiveDisplay = LiveDisplayManager.getInstance(mContext); + if (mLiveDisplay.getConfig() != null) { + mOutdoorModeAvailable = mLiveDisplay.getConfig().hasFeature(MODE_OUTDOOR) && + !mLiveDisplay.getConfig().hasFeature(FEATURE_MANAGED_OUTDOOR_MODE); + mDayTemperature = mLiveDisplay.getDayColorTemperature(); + } else { + mOutdoorModeAvailable = false; + mDayTemperature = -1; + } + + mObserver = new LiveDisplayObserver(mHandler); + mObserver.startObserving(); + } + + private void updateEntries() { + Resources res = mContext.getResources(); + mTitle = res.getString(R.string.live_display_title); + mEntries = res.getStringArray(R.array.live_display_entries); + mDescriptionEntries = res.getStringArray(R.array.live_display_description); + mAnnouncementEntries = res.getStringArray(R.array.live_display_announcement); + mValues = res.getStringArray(R.array.live_display_values); + } + + @Override + public boolean isAvailable() { + return !ColorDisplayManager.isNightDisplayAvailable(mContext); + } + + @Override + public LiveDisplayState newTileState() { + return new LiveDisplayState(); + } + + @Override + public void handleSetListening(boolean listening) { + if (mListening == listening) + return; + mListening = listening; + if (listening) { + mObserver.startObserving(); + } else { + mObserver.endObserving(); + } + } + + @Override + protected void handleClick() { + changeToNextMode(); + } + + @Override + protected void handleUpdateState(LiveDisplayState state, Object arg) { + updateEntries(); + state.mode = arg == null ? getCurrentModeIndex() : (Integer) arg; + state.label = mTitle; + state.secondaryLabel = mEntries[state.mode]; + state.icon = ResourceIcon.get(mEntryIconRes[state.mode]); + state.contentDescription = mDescriptionEntries[state.mode]; + state.state = mLiveDisplay.getMode() != MODE_OFF ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; + } + + @Override + public int getMetricsCategory() { + return MetricsEvent.REVENGEOS; + } + + @Override + public CharSequence getTileLabel() { + return mContext.getString(R.string.live_display_title); + } + + @Override + public Intent getLongClickIntent() { + return LIVEDISPLAY_SETTINGS; + } + + @Override + protected String composeChangeAnnouncement() { + return mAnnouncementEntries[getCurrentModeIndex()]; + } + + private int getCurrentModeIndex() { + String currentLiveDisplayMode = null; + try { + currentLiveDisplayMode = String.valueOf(mLiveDisplay.getMode()); + } catch (NullPointerException e) { + currentLiveDisplayMode = String.valueOf(MODE_AUTO); + } finally { + return ArrayUtils.indexOf(mValues, currentLiveDisplayMode); + } + } + + private void changeToNextMode() { + int next = getCurrentModeIndex() + 1; + + if (next >= mValues.length) { + next = 0; + } + + int nextMode = 0; + + while (true) { + nextMode = Integer.valueOf(mValues[next]); + // Skip outdoor mode if it's unsupported, and skip the day setting + // if it's the same as the off setting + if ((!mOutdoorModeAvailable && nextMode == MODE_OUTDOOR) || + (mDayTemperature == OFF_TEMPERATURE && nextMode == MODE_DAY)) { + next++; + if (next >= mValues.length) { + next = 0; + } + } else { + break; + } + } + + mLiveDisplay.setMode(nextMode); + } + + private class LiveDisplayObserver extends ContentObserver { + public LiveDisplayObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange) { + mDayTemperature = mLiveDisplay.getDayColorTemperature(); + refreshState(getCurrentModeIndex()); + } + + public void startObserving() { + mContext.getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.System.DISPLAY_TEMPERATURE_MODE), + false, this, UserHandle.USER_ALL); + mContext.getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.System.DISPLAY_TEMPERATURE_DAY), + false, this, UserHandle.USER_ALL); + } + + public void endObserving() { + mContext.getContentResolver().unregisterContentObserver(this); + } + } +} From 34d281413861763b93ebd85d8b088ee8d3fc8ece Mon Sep 17 00:00:00 2001 From: Henrique Silva Date: Mon, 6 May 2019 15:51:57 -0300 Subject: [PATCH 09/22] LiveDisplay: don't start services if phone is encrypted Change-Id: I7d4347fa7dcf3b2d768cb3903aa5c89de373647b Signed-off-by: DennySPb --- services/java/com/android/server/SystemServer.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 4c9ff0e7f623..f50ac4a61dcf 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -2026,12 +2026,14 @@ private void startOtherServices(@NonNull TimingsTraceAndSlog t) { } // LiveDisplay - t.traceBegin("StartLineageHardwareService"); - mSystemServiceManager.startService(LineageHardwareService.class); - t.traceEnd(); - t.traceBegin("StartLiveDisplayService"); - mSystemServiceManager.startService(LiveDisplayService.class); - t.traceEnd(); + if (!mOnlyCore){ + t.traceBegin("StartLineageHardwareService"); + mSystemServiceManager.startService(LineageHardwareService.class); + t.traceEnd(); + t.traceBegin("StartLiveDisplayService"); + mSystemServiceManager.startService(LiveDisplayService.class); + t.traceEnd(); + } } if (!isWatch) { From 7a295baa588cdf66306bfc8275d8f5bb96430113 Mon Sep 17 00:00:00 2001 From: Paul Keith Date: Mon, 18 Feb 2019 00:04:57 +0100 Subject: [PATCH 10/22] LiveDisplay: Change night/day mode transition behavior * Update 2x a minute rather than 1x a minute as a result of dropping TWILIGHT_ADJUSTMENT_TIME from 1hr to 30min in an earlier change * Use an AccelerateDecelerateInterpolator for transitioning from day to night mode and back to make the transition smoother for the user * Update copyright while we're at it Change-Id: I438f265c6cd9a2487b75744beccaf2bbac21b36e Signed-off-by: DennySPB Signed-off-by: DennySPb --- .../display/ColorTemperatureController.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/services/core/java/com/android/server/custom/display/ColorTemperatureController.java b/services/core/java/com/android/server/custom/display/ColorTemperatureController.java index 7a05966b9be8..4c871ea81599 100644 --- a/services/core/java/com/android/server/custom/display/ColorTemperatureController.java +++ b/services/core/java/com/android/server/custom/display/ColorTemperatureController.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 The CyanogenMod Project + * 2018-2019 The LineageOS Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +25,7 @@ import android.util.MathUtils; import android.util.Range; import android.util.Slog; -import android.view.animation.LinearInterpolator; +import android.view.animation.AccelerateDecelerateInterpolator; import com.android.server.custom.display.TwilightTracker.TwilightState; @@ -58,6 +59,7 @@ public class ColorTemperatureController extends LiveDisplayFeature { private int mDayTemperature; private int mNightTemperature; + private AccelerateDecelerateInterpolator mInterpolator; private ValueAnimator mAnimator; private final LineageHardwareManager mHardware; @@ -97,6 +99,8 @@ public ColorTemperatureController(Context context, mColorTemperatureRange.getLower(), mDefaultDayTemperature, mColorTemperatureRange.getUpper()); + + mInterpolator = new AccelerateDecelerateInterpolator(); } @Override @@ -202,8 +206,8 @@ private synchronized void updateColorTemperature() { setDisplayTemperature(temperature); if (isTransitioning()) { - // fire again in a minute - mHandler.postDelayed(mTransitionRunnable, DateUtils.MINUTE_IN_MILLIS); + // fire again in 30 seconds + mHandler.postDelayed(mTransitionRunnable, DateUtils.MINUTE_IN_MILLIS / 2); } } @@ -234,7 +238,7 @@ private synchronized void animateColorBalance(int balance) { mAnimator = ValueAnimator.ofInt(current, balance); mAnimator.setDuration(duration); - mAnimator.setInterpolator(new LinearInterpolator()); + mAnimator.setInterpolator(mInterpolator); mAnimator.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(final ValueAnimator animation) { @@ -291,7 +295,7 @@ private synchronized void setDisplayTemperature(int temperature) { * @param sunrise * @return float between 0 and 1 */ - private static float adj(long now, long sunset, long sunrise) { + private float adj(long now, long sunset, long sunrise) { if (sunset < 0 || sunrise < 0 || now < (sunset - TWILIGHT_ADJUSTMENT_TIME) || now > (sunrise + TWILIGHT_ADJUSTMENT_TIME)) { @@ -301,12 +305,12 @@ private static float adj(long now, long sunset, long sunrise) { // Scale the transition into night mode in 0.5hr before civil sunset if (now <= sunset) { - return (float) (sunset - now) / TWILIGHT_ADJUSTMENT_TIME; + return mInterpolator.getInterpolation((float) (sunset - now) / TWILIGHT_ADJUSTMENT_TIME); } // Scale the transition into day mode in 0.5hr after civil sunrise if (now >= sunrise) { - return (float) (now - sunrise) / TWILIGHT_ADJUSTMENT_TIME; + return mInterpolator.getInterpolation((float) (now - sunrise) / TWILIGHT_ADJUSTMENT_TIME); } // More than 0.5hr past civil sunset From 2b04f2a6032dc2f58b3c187afe4d2a6f82d6a8e7 Mon Sep 17 00:00:00 2001 From: Paul Keith Date: Wed, 22 May 2019 10:19:53 +0300 Subject: [PATCH 11/22] LiveDisplay: Use Google's algorithm to convert color temperature to RGB * Google's works better than ours does Signed-off-by: DennySPB Change-Id: I5fdce3a2c2bac758772effdb59d25b3ef6854a7b Signed-off-by: DennySPB Signed-off-by: DennySPb --- .../internal/util/custom/ColorUtils.java | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/core/java/com/android/internal/util/custom/ColorUtils.java b/core/java/com/android/internal/util/custom/ColorUtils.java index 6167df9fb743..f2756f3bef8f 100644 --- a/core/java/com/android/internal/util/custom/ColorUtils.java +++ b/core/java/com/android/internal/util/custom/ColorUtils.java @@ -15,6 +15,8 @@ */ package com.android.internal.util.custom; +import android.app.ActivityThread; + import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; @@ -38,6 +40,17 @@ public class ColorUtils { Color.BLUE, Color.MAGENTA, Color.WHITE, Color.BLACK }; + private static float[] COEFFICIENTS = new float[9]; + + static { + String[] coefficients = ActivityThread.currentApplication().getApplicationContext() + .getResources().getStringArray( + com.android.internal.R.array.config_nightDisplayColorTemperatureCoefficients); + for (int i = 0; i < 9 && i < coefficients.length; i++) { + COEFFICIENTS[i] = Float.valueOf(coefficients[i]); + } + } + /** * Drop the alpha component from an RGBA packed int and return * a non sign-extended RGB int. @@ -316,16 +329,17 @@ public static int generateAlertColorFromDrawable(Drawable drawable) { * @param degreesK * @return array of floats representing rgb values 0->1 */ - public static float[] temperatureToRGB(int degreesK) { - int k = MathUtils.constrain(degreesK, 1000, 20000); - float a = (k % 100) / 100.0f; - int i = ((k - 1000)/ 100) * 3; - return new float[] { interp(i, a), interp(i+1, a), interp(i+2, a) }; - } + public static float[] temperatureToRGB(int degreesK) { + float[] rgb = new float[3]; + + final float square = degreesK * degreesK; + for (int i = 0; i < rgb.length; i++) { + rgb[i] = square * COEFFICIENTS[i * 3] + + degreesK * COEFFICIENTS[i * 3 + 1] + COEFFICIENTS[i * 3 + 2]; + } - private static float interp(int i, float a) { - return MathUtils.lerp((float)sColorTable[i], (float)sColorTable[i+3], a); + return rgb; } /** From 83a611d7399d7edd4126f16baa42678438b60adb Mon Sep 17 00:00:00 2001 From: Bruno Martins Date: Tue, 4 Jun 2019 22:14:10 +0100 Subject: [PATCH 12/22] LiveDisplayService: Disable ColorTemperature when NightDisplay is available * Having decided to use AOSP's night display feature in favor of our equivalent implementation, then we should take care of getting the latter completely disabled. Not only it can happen that a user was likely using it and enabling night display will now cause color transformation matrices to be applied twice, but also because there are resources being used for nothing. Change-Id: I4ba7f643bf9e3d3b9bda17102b41668644e22ba1 Signed-off-by: DennySPB Signed-off-by: DennySPb --- .../server/custom/display/LiveDisplayService.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/services/core/java/com/android/server/custom/display/LiveDisplayService.java b/services/core/java/com/android/server/custom/display/LiveDisplayService.java index c5be49af1519..6d85376846c7 100644 --- a/services/core/java/com/android/server/custom/display/LiveDisplayService.java +++ b/services/core/java/com/android/server/custom/display/LiveDisplayService.java @@ -34,6 +34,7 @@ import android.os.UserHandle; import android.view.Display; +import com.android.internal.app.ColorDisplayController; import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.SystemService; @@ -143,6 +144,7 @@ public void onStart() { @Override public void onBootPhase(int phase) { if (phase == PHASE_BOOT_COMPLETED) { + final boolean isNightDisplayAvailable = ColorDisplayController.isAvailable(mContext); mAwaitingNudge = getSunsetCounter() < 1; @@ -150,7 +152,9 @@ public void onBootPhase(int phase) { mFeatures.add(mDHC); mCTC = new ColorTemperatureController(mContext, mHandler, mDHC); - mFeatures.add(mCTC); + if (!isNightDisplayAvailable) { + mFeatures.add(mCTC); + } mOMC = new OutdoorModeController(mContext, mHandler); mFeatures.add(mOMC); @@ -171,7 +175,8 @@ public void onBootPhase(int phase) { int defaultMode = mContext.getResources().getInteger( com.android.internal.R.integer.config_defaultLiveDisplayMode); - mConfig = new LiveDisplayConfig(capabilities, defaultMode, + mConfig = new LiveDisplayConfig(capabilities, + isNightDisplayAvailable ? MODE_OFF : defaultMode, mCTC.getDefaultDayTemperature(), mCTC.getDefaultNightTemperature(), mOMC.getDefaultAutoOutdoorMode(), mDHC.getDefaultAutoContrast(), mDHC.getDefaultCABC(), mDHC.getDefaultColorEnhancement(), @@ -193,8 +198,10 @@ public void onBootPhase(int phase) { mState.mLowPowerMode = pmi.getLowPowerState(SERVICE_TYPE_DUMMY).globalBatterySaverEnabled; - mTwilightTracker.registerListener(mTwilightListener, mHandler); - mState.mTwilight = mTwilightTracker.getCurrentState(); + if (!isNightDisplayAvailable) { + mTwilightTracker.registerListener(mTwilightListener, mHandler); + mState.mTwilight = mTwilightTracker.getCurrentState(); + } if (mConfig.hasModeSupport()) { mModeObserver = new ModeObserver(mHandler); From 6e6f606ec43453ab69f65b932565fec934b14b09 Mon Sep 17 00:00:00 2001 From: dianlujitao Date: Thu, 8 Aug 2019 16:32:50 +0800 Subject: [PATCH 13/22] LiveDisplayService: Notify SystemUI after initialization finished Change-Id: Id01eeee3bdfc599bdc20a5779db3d002fa4bc1c9 Signed-off-by: DennySPB Signed-off-by: DennySPb --- core/res/AndroidManifest.xml | 1 + .../com/android/server/custom/display/LiveDisplayService.java | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 5be9b50f743c..dbfdb2d45ab8 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5076,6 +5076,7 @@ + Date: Wed, 7 Aug 2019 00:27:50 +0800 Subject: [PATCH 14/22] LiveDisplayTile: Enable for outdoor mode and skip night display on HWC2 Change-Id: I00ab654033827388f8063eafb98a89315cb47aa9 Signed-off-by: DennySPB Signed-off-by: DennySPb --- .../systemui/qs/tiles/LiveDisplayTile.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LiveDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LiveDisplayTile.java index 6ae9b93062a3..d072a0d19bfe 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LiveDisplayTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LiveDisplayTile.java @@ -20,6 +20,7 @@ import static com.android.internal.custom.hardware.LiveDisplayManager.FEATURE_MANAGED_OUTDOOR_MODE; import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_AUTO; import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_DAY; +import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_NIGHT; import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_OFF; import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_OUTDOOR; @@ -30,9 +31,9 @@ import android.hardware.display.ColorDisplayManager; import android.os.Handler; import android.os.UserHandle; -import android.provider.Settings; import android.service.quicksettings.Tile; +import com.android.internal.app.ColorDisplayController; import com.android.internal.util.ArrayUtils; import com.android.systemui.plugins.qs.QSTile.LiveDisplayState; import com.android.systemui.qs.QSHost; @@ -64,6 +65,7 @@ public class LiveDisplayTile extends QSTileImpl { private int mDayTemperature; + private final boolean mNightDisplayAvailable; private final boolean mOutdoorModeAvailable; private final LiveDisplayManager mLiveDisplay; @@ -73,6 +75,7 @@ public class LiveDisplayTile extends QSTileImpl { @Inject public LiveDisplayTile(QSHost host) { super(host); + mNightDisplayAvailable = ColorDisplayController.isAvailable(mContext); Resources res = mContext.getResources(); TypedArray typedArray = res.obtainTypedArray(R.array.live_display_drawables); @@ -109,7 +112,7 @@ private void updateEntries() { @Override public boolean isAvailable() { - return !ColorDisplayManager.isNightDisplayAvailable(mContext); + return !mNightDisplayAvailable || mOutdoorModeAvailable; } @Override @@ -187,10 +190,12 @@ private void changeToNextMode() { while (true) { nextMode = Integer.valueOf(mValues[next]); - // Skip outdoor mode if it's unsupported, and skip the day setting - // if it's the same as the off setting + // Skip outdoor mode if it's unsupported, skip the day setting + // if it's the same as the off setting, and skip night display + // on HWC2 if ((!mOutdoorModeAvailable && nextMode == MODE_OUTDOOR) || - (mDayTemperature == OFF_TEMPERATURE && nextMode == MODE_DAY)) { + (mDayTemperature == OFF_TEMPERATURE && nextMode == MODE_DAY) || + (mNightDisplayAvailable && (nextMode == MODE_DAY || nextMode == MODE_NIGHT))) { next++; if (next >= mValues.length) { next = 0; From aaf2499c0a7284158e8ea0f2c63ff4e95bdf44f3 Mon Sep 17 00:00:00 2001 From: dianlujitao Date: Wed, 7 Aug 2019 19:38:32 +0800 Subject: [PATCH 15/22] LiveDisplayTile: Refresh state after livedisplay initialization * SystemUI starts earlier than LiveDisplayConfig initialized, so there's no easy way to determin whether to make it available. * Instead, receive broadcast incicating initialization completed from LiveDisplayService and gray out on desire. * Outdoor mode gets fixed by the way. Change-Id: I55cea14c9248da1186e6c0c1b9e719a2bcf5444d Signed-off-by: DennySPB Signed-off-by: DennySPb --- .../systemui/qs/tiles/LiveDisplayTile.java | 57 ++++++++++++++----- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LiveDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LiveDisplayTile.java index d072a0d19bfe..f391789b7028 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LiveDisplayTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LiveDisplayTile.java @@ -24,7 +24,10 @@ import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_OFF; import static com.android.internal.custom.hardware.LiveDisplayManager.MODE_OUTDOOR; +import android.content.BroadcastReceiver; +import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.res.Resources; import android.content.res.TypedArray; import android.database.ContentObserver; @@ -63,10 +66,11 @@ public class LiveDisplayTile extends QSTileImpl { private boolean mListening; - private int mDayTemperature; + private int mDayTemperature = -1; private final boolean mNightDisplayAvailable; - private final boolean mOutdoorModeAvailable; + private boolean mOutdoorModeAvailable; + private boolean mReceiverRegistered; private final LiveDisplayManager mLiveDisplay; @@ -88,17 +92,37 @@ public LiveDisplayTile(QSHost host) { updateEntries(); mLiveDisplay = LiveDisplayManager.getInstance(mContext); + if (!updateConfig()) { + mContext.registerReceiver(mReceiver, new IntentFilter( + "lineageos.intent.action.INITIALIZE_LIVEDISPLAY")); + mReceiverRegistered = true; + } + + mObserver = new LiveDisplayObserver(mHandler); + mObserver.startObserving(); + } + + @Override + protected void handleDestroy() { + super.handleDestroy(); + unregisterReceiver(); + } + + private void unregisterReceiver() { + if (mReceiverRegistered) { + mContext.unregisterReceiver(mReceiver); + mReceiverRegistered = false; + } + } + + private boolean updateConfig() { if (mLiveDisplay.getConfig() != null) { mOutdoorModeAvailable = mLiveDisplay.getConfig().hasFeature(MODE_OUTDOOR) && !mLiveDisplay.getConfig().hasFeature(FEATURE_MANAGED_OUTDOOR_MODE); mDayTemperature = mLiveDisplay.getDayColorTemperature(); - } else { - mOutdoorModeAvailable = false; - mDayTemperature = -1; + return true; } - - mObserver = new LiveDisplayObserver(mHandler); - mObserver.startObserving(); + return false; } private void updateEntries() { @@ -110,11 +134,6 @@ private void updateEntries() { mValues = res.getStringArray(R.array.live_display_values); } - @Override - public boolean isAvailable() { - return !mNightDisplayAvailable || mOutdoorModeAvailable; - } - @Override public LiveDisplayState newTileState() { return new LiveDisplayState(); @@ -145,7 +164,8 @@ protected void handleUpdateState(LiveDisplayState state, Object arg) { state.secondaryLabel = mEntries[state.mode]; state.icon = ResourceIcon.get(mEntryIconRes[state.mode]); state.contentDescription = mDescriptionEntries[state.mode]; - state.state = mLiveDisplay.getMode() != MODE_OFF ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; + state.state = (mNightDisplayAvailable && !mOutdoorModeAvailable) ? Tile.STATE_UNAVAILABLE: + mLiveDisplay.getMode() != MODE_OFF ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; } @Override @@ -232,4 +252,13 @@ public void endObserving() { mContext.getContentResolver().unregisterContentObserver(this); } } + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + updateConfig(); + refreshState(); + unregisterReceiver(); + } + }; } From f4d484fccdee08da1db06dead39203ed7704206d Mon Sep 17 00:00:00 2001 From: dianlujitao Date: Wed, 7 Aug 2019 15:52:22 +0800 Subject: [PATCH 16/22] LiveDisplayService: Properly disable ColorTemperature This reverts commit f781a9399aeb3a6c4db78a8848188781c7c0720a. * Twilight tracker and display mode are still used by ColorTemperatureController Change-Id: Icaf2a93befd56478eb2307d6f25da49c7c3fd73f Signed-off-by: DennySPB Signed-off-by: DennySPb --- .../display/ColorTemperatureController.java | 4 ++-- .../server/custom/display/LiveDisplayFeature.java | 4 ++++ .../server/custom/display/LiveDisplayService.java | 15 ++++----------- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/services/core/java/com/android/server/custom/display/ColorTemperatureController.java b/services/core/java/com/android/server/custom/display/ColorTemperatureController.java index 4c871ea81599..3de5d54e312e 100644 --- a/services/core/java/com/android/server/custom/display/ColorTemperatureController.java +++ b/services/core/java/com/android/server/custom/display/ColorTemperatureController.java @@ -81,8 +81,8 @@ public ColorTemperatureController(Context context, .isSupported(LineageHardwareManager.FEATURE_COLOR_BALANCE); mColorBalanceRange = mHardware.getColorBalanceRange(); - mUseTemperatureAdjustment = mUseColorBalance || - mDisplayHardware.hasColorAdjustment(); + mUseTemperatureAdjustment = !mNightDisplayAvailable && + (mUseColorBalance || mDisplayHardware.hasColorAdjustment()); mDefaultDayTemperature = mContext.getResources().getInteger( com.android.internal.R.integer.config_dayColorTemperature); diff --git a/services/core/java/com/android/server/custom/display/LiveDisplayFeature.java b/services/core/java/com/android/server/custom/display/LiveDisplayFeature.java index c643428b94ee..6107247e4420 100644 --- a/services/core/java/com/android/server/custom/display/LiveDisplayFeature.java +++ b/services/core/java/com/android/server/custom/display/LiveDisplayFeature.java @@ -36,6 +36,8 @@ import static com.android.server.custom.display.LiveDisplayService.MODE_CHANGED; import static com.android.server.custom.display.LiveDisplayService.TWILIGHT_CHANGED; +import com.android.internal.app.ColorDisplayController; + public abstract class LiveDisplayFeature { protected static final String TAG = "LiveDisplay"; @@ -43,6 +45,7 @@ public abstract class LiveDisplayFeature { protected final Context mContext; protected final Handler mHandler; + protected final boolean mNightDisplayAvailable; private SettingsObserver mSettingsObserver; private State mState; @@ -50,6 +53,7 @@ public abstract class LiveDisplayFeature { public LiveDisplayFeature(Context context, Handler handler) { mContext = context; mHandler = handler; + mNightDisplayAvailable = ColorDisplayController.isAvailable(mContext); } public abstract void onStart(); diff --git a/services/core/java/com/android/server/custom/display/LiveDisplayService.java b/services/core/java/com/android/server/custom/display/LiveDisplayService.java index ca4cb2dee8ad..d8ec9441eea2 100644 --- a/services/core/java/com/android/server/custom/display/LiveDisplayService.java +++ b/services/core/java/com/android/server/custom/display/LiveDisplayService.java @@ -34,7 +34,6 @@ import android.os.UserHandle; import android.view.Display; -import com.android.internal.app.ColorDisplayController; import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.SystemService; @@ -144,7 +143,6 @@ public void onStart() { @Override public void onBootPhase(int phase) { if (phase == PHASE_BOOT_COMPLETED) { - final boolean isNightDisplayAvailable = ColorDisplayController.isAvailable(mContext); mAwaitingNudge = getSunsetCounter() < 1; @@ -152,9 +150,7 @@ public void onBootPhase(int phase) { mFeatures.add(mDHC); mCTC = new ColorTemperatureController(mContext, mHandler, mDHC); - if (!isNightDisplayAvailable) { - mFeatures.add(mCTC); - } + mFeatures.add(mCTC); mOMC = new OutdoorModeController(mContext, mHandler); mFeatures.add(mOMC); @@ -175,8 +171,7 @@ public void onBootPhase(int phase) { int defaultMode = mContext.getResources().getInteger( com.android.internal.R.integer.config_defaultLiveDisplayMode); - mConfig = new LiveDisplayConfig(capabilities, - isNightDisplayAvailable ? MODE_OFF : defaultMode, + mConfig = new LiveDisplayConfig(capabilities, defaultMode, mCTC.getDefaultDayTemperature(), mCTC.getDefaultNightTemperature(), mOMC.getDefaultAutoOutdoorMode(), mDHC.getDefaultAutoContrast(), mDHC.getDefaultCABC(), mDHC.getDefaultColorEnhancement(), @@ -198,10 +193,8 @@ public void onBootPhase(int phase) { mState.mLowPowerMode = pmi.getLowPowerState(SERVICE_TYPE_DUMMY).globalBatterySaverEnabled; - if (!isNightDisplayAvailable) { - mTwilightTracker.registerListener(mTwilightListener, mHandler); - mState.mTwilight = mTwilightTracker.getCurrentState(); - } + mTwilightTracker.registerListener(mTwilightListener, mHandler); + mState.mTwilight = mTwilightTracker.getCurrentState(); if (mConfig.hasModeSupport()) { mModeObserver = new ModeObserver(mHandler); From 8b62ef1f6c2d88a19845aaf7bf7c88a90d0e93d7 Mon Sep 17 00:00:00 2001 From: dianlujitao Date: Tue, 6 Aug 2019 23:23:42 +0800 Subject: [PATCH 17/22] OutdoorModeController: Advertise MODE_AUTO * This capability is set by ColorTemperatureController on HWC1 which is not used on HWC2, but it controls whether to enable automatic outdoor mode, thus advertise it here. * Set the default mode back to MODE_AUTO. Change-Id: I2061cdb9ca5991e60dc9c4b3d566dd811c3c8936 Signed-off-by: DennySPB Signed-off-by: DennySPb --- .../android/server/custom/display/OutdoorModeController.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/core/java/com/android/server/custom/display/OutdoorModeController.java b/services/core/java/com/android/server/custom/display/OutdoorModeController.java index ccffb5384bbc..cedef74f6f69 100644 --- a/services/core/java/com/android/server/custom/display/OutdoorModeController.java +++ b/services/core/java/com/android/server/custom/display/OutdoorModeController.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2016 The CyanogenMod Project + * 2019 The LineageOS Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -84,6 +85,7 @@ public void onStart() { @Override public boolean getCapabilities(final BitSet caps) { if (mUseOutdoorMode) { + caps.set(LiveDisplayManager.MODE_AUTO); caps.set(LiveDisplayManager.MODE_OUTDOOR); if (mSelfManaged) { caps.set(LiveDisplayManager.FEATURE_MANAGED_OUTDOOR_MODE); From 3b9df2f9aa1d747b77440e1deb08c647a98d4b88 Mon Sep 17 00:00:00 2001 From: dianlujitao Date: Tue, 6 Aug 2019 23:59:53 +0800 Subject: [PATCH 18/22] OutdoorModeController: Unconditionally enable auto mode on HWC2 * On HWC2, setting "Display mode" to "Automatic" and disabling "Automatic outdoor mode" at the same time makes no difference with setting "Display mode" to "Off" alone. Obviously this is redundant and could confuse the user, so simply ignore the "Automatic outdoor mode" setting on HWC2 to reduce redundancy. Change-Id: I6858571a6b9ded3540fd3d936499878f57a7b3d4 Signed-off-by: DennySPB Signed-off-by: DennySPb --- .../android/server/custom/display/OutdoorModeController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/core/java/com/android/server/custom/display/OutdoorModeController.java b/services/core/java/com/android/server/custom/display/OutdoorModeController.java index cedef74f6f69..68893c389b5a 100644 --- a/services/core/java/com/android/server/custom/display/OutdoorModeController.java +++ b/services/core/java/com/android/server/custom/display/OutdoorModeController.java @@ -249,9 +249,9 @@ boolean setAutomaticOutdoorModeEnabled(boolean enabled) { } boolean isAutomaticOutdoorModeEnabled() { - return mUseOutdoorMode && + return mUseOutdoorMode && (mNightDisplayAvailable || getBoolean(Settings.System.DISPLAY_AUTO_OUTDOOR_MODE, - getDefaultAutoOutdoorMode()); + getDefaultAutoOutdoorMode())); } boolean getDefaultAutoOutdoorMode() { From b2a4539fb4db81307740db8830f1446aae9dd27e Mon Sep 17 00:00:00 2001 From: dianlujitao Date: Wed, 7 Aug 2019 14:51:40 +0800 Subject: [PATCH 19/22] LiveDisplayManager: Perform null check in getConfig() * LiveDisplayConfig isn't instanced until boot completed, thus if LiveDisplayManager is instanced earlier, null is always returned. Change-Id: I003886ffced86a5a82dec25a4cc7b542da0f2331 Signed-off-by: DennySPB Signed-off-by: DennySPb --- .../custom/hardware/LiveDisplayManager.java | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/core/java/com/android/internal/custom/hardware/LiveDisplayManager.java b/core/java/com/android/internal/custom/hardware/LiveDisplayManager.java index ce5f2cbdebaa..6e0da47448a7 100644 --- a/core/java/com/android/internal/custom/hardware/LiveDisplayManager.java +++ b/core/java/com/android/internal/custom/hardware/LiveDisplayManager.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2016 The CyanogenMod Project - * 2018 The LineageOS Project + * 2018-2019 The LineageOS Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -139,7 +139,7 @@ public class LiveDisplayManager { private static final String TAG = "LiveDisplay"; private final Context mContext; - private final LiveDisplayConfig mConfig; + private LiveDisplayConfig mConfig; private static LiveDisplayManager sInstance; private static ILiveDisplayService sService; @@ -161,15 +161,6 @@ private LiveDisplayManager(Context context) { " crashed, was not started, or the interface has been called to early in" + " SystemServer init"); } - - try { - mConfig = sService.getConfig(); - if (mConfig == null) { - Log.w(TAG, "Unable to get LiveDisplay configuration!"); - } - } catch (RemoteException e) { - throw new RuntimeException("Unable to fetch LiveDisplay configuration!", e); - } } /** @@ -214,7 +205,14 @@ private boolean checkService() { * @return the configuration */ public LiveDisplayConfig getConfig() { - return mConfig; + try { + if (mConfig == null) { + mConfig = checkService() ? sService.getConfig() : null; + } + return mConfig; + } catch (RemoteException e) { + return null; + } } /** From a03a00116db3919f7851680252a5e1fab0fe65ea Mon Sep 17 00:00:00 2001 From: jhenrique09 Date: Wed, 21 Aug 2019 13:58:40 -0300 Subject: [PATCH 20/22] LiveDisplayTile: Remove tile if unavailable Change-Id: Ic05ff73c57d3f02cd2752acb1b1df2564c0d4b6b Signed-off-by: DennySPB Signed-off-by: DennySPb --- .../src/com/android/systemui/qs/tiles/LiveDisplayTile.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LiveDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LiveDisplayTile.java index f391789b7028..42e1ab14e15c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LiveDisplayTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LiveDisplayTile.java @@ -120,6 +120,9 @@ private boolean updateConfig() { mOutdoorModeAvailable = mLiveDisplay.getConfig().hasFeature(MODE_OUTDOOR) && !mLiveDisplay.getConfig().hasFeature(FEATURE_MANAGED_OUTDOOR_MODE); mDayTemperature = mLiveDisplay.getDayColorTemperature(); + if (mNightDisplayAvailable && !mOutdoorModeAvailable){ + mHost.removeTile(getTileSpec()); + } return true; } return false; From f057a1583014a7aee8ed27bb8abc26679983981e Mon Sep 17 00:00:00 2001 From: LuK1337 Date: Fri, 6 Sep 2019 23:41:14 +0200 Subject: [PATCH 21/22] LiveDisplay: Switch to ColorDisplayManager * ColorDisplayController is dead Change-Id: I9b5fdc3546bea7aabd65877c1862d9eaebbd4af3 Signed-off-by: DennySPB Signed-off-by: DennySPb --- .../src/com/android/systemui/qs/tiles/LiveDisplayTile.java | 4 ++-- .../com/android/server/custom/display/LiveDisplayFeature.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LiveDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LiveDisplayTile.java index 42e1ab14e15c..1853d6c41396 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LiveDisplayTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LiveDisplayTile.java @@ -32,11 +32,11 @@ import android.content.res.TypedArray; import android.database.ContentObserver; import android.hardware.display.ColorDisplayManager; +import android.provider.Settings; import android.os.Handler; import android.os.UserHandle; import android.service.quicksettings.Tile; -import com.android.internal.app.ColorDisplayController; import com.android.internal.util.ArrayUtils; import com.android.systemui.plugins.qs.QSTile.LiveDisplayState; import com.android.systemui.qs.QSHost; @@ -79,7 +79,7 @@ public class LiveDisplayTile extends QSTileImpl { @Inject public LiveDisplayTile(QSHost host) { super(host); - mNightDisplayAvailable = ColorDisplayController.isAvailable(mContext); + mNightDisplayAvailable = ColorDisplayManager.isNightDisplayAvailable(mContext); Resources res = mContext.getResources(); TypedArray typedArray = res.obtainTypedArray(R.array.live_display_drawables); diff --git a/services/core/java/com/android/server/custom/display/LiveDisplayFeature.java b/services/core/java/com/android/server/custom/display/LiveDisplayFeature.java index 6107247e4420..9cdb7062f1be 100644 --- a/services/core/java/com/android/server/custom/display/LiveDisplayFeature.java +++ b/services/core/java/com/android/server/custom/display/LiveDisplayFeature.java @@ -17,6 +17,7 @@ import android.content.ContentResolver; import android.content.Context; +import android.hardware.display.ColorDisplayManager; import android.net.Uri; import android.os.Handler; import android.os.UserHandle; @@ -36,7 +37,6 @@ import static com.android.server.custom.display.LiveDisplayService.MODE_CHANGED; import static com.android.server.custom.display.LiveDisplayService.TWILIGHT_CHANGED; -import com.android.internal.app.ColorDisplayController; public abstract class LiveDisplayFeature { @@ -53,7 +53,7 @@ public abstract class LiveDisplayFeature { public LiveDisplayFeature(Context context, Handler handler) { mContext = context; mHandler = handler; - mNightDisplayAvailable = ColorDisplayController.isAvailable(mContext); + mNightDisplayAvailable = ColorDisplayManager.isNightDisplayAvailable(mContext); } public abstract void onStart(); From d1e20827d2fb5488400537a3c85456311e3d7681 Mon Sep 17 00:00:00 2001 From: DennySPB Date: Mon, 23 Sep 2019 16:52:22 +0300 Subject: [PATCH 22/22] LiveDisplayTile: fixup modes cycling for aosp implementation Signed-off-by: DennySPB Change-Id: I5edcef553ae32b4eb72882ef81ffe56f5074fc0d Signed-off-by: DennySPb --- .../src/com/android/systemui/qs/tiles/LiveDisplayTile.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LiveDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LiveDisplayTile.java index 1853d6c41396..65b5c30dac7e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LiveDisplayTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LiveDisplayTile.java @@ -228,7 +228,9 @@ private void changeToNextMode() { } } - mLiveDisplay.setMode(nextMode); + Settings.System.putIntForUser(mContext.getContentResolver(), + Settings.System.DISPLAY_TEMPERATURE_MODE, nextMode, + UserHandle.USER_CURRENT); } private class LiveDisplayObserver extends ContentObserver {