blob: 7fb5b191a9b04ddc13b53ed616abad5e07615ee0 [file] [log] [blame]
/*
* Copyright (C) 2016 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.display.color;
import static android.hardware.display.ColorDisplayManager.AUTO_MODE_CUSTOM_TIME;
import static android.hardware.display.ColorDisplayManager.AUTO_MODE_DISABLED;
import static android.hardware.display.ColorDisplayManager.AUTO_MODE_TWILIGHT;
import static android.hardware.display.ColorDisplayManager.COLOR_MODE_AUTOMATIC;
import static android.hardware.display.ColorDisplayManager.COLOR_MODE_BOOSTED;
import static android.hardware.display.ColorDisplayManager.COLOR_MODE_NATURAL;
import static android.hardware.display.ColorDisplayManager.COLOR_MODE_SATURATED;
import static android.hardware.display.ColorDisplayManager.VENDOR_COLOR_MODE_RANGE_MAX;
import static android.hardware.display.ColorDisplayManager.VENDOR_COLOR_MODE_RANGE_MIN;
import static com.android.server.display.color.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
import android.Manifest;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
import android.annotation.UserIdInt;
import android.app.AlarmManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.display.ColorDisplayManager;
import android.hardware.display.ColorDisplayManager.AutoMode;
import android.hardware.display.ColorDisplayManager.ColorMode;
import android.hardware.display.IColorDisplayManager;
import android.hardware.display.Time;
import android.net.Uri;
import android.opengl.Matrix;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings.Secure;
import android.provider.Settings.System;
import android.util.MathUtils;
import android.util.Slog;
import android.util.SparseIntArray;
import android.view.Display;
import android.view.SurfaceControl;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.AnimationUtils;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.server.DisplayThread;
import com.android.server.SystemService;
import com.android.server.twilight.TwilightListener;
import com.android.server.twilight.TwilightManager;
import com.android.server.twilight.TwilightState;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.time.DateTimeException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeParseException;
/**
* Controls the display's color transforms.
*/
public final class ColorDisplayService extends SystemService {
static final String TAG = "ColorDisplayService";
/**
* The identity matrix, used if one of the given matrices is {@code null}.
*/
static final float[] MATRIX_IDENTITY = new float[16];
static {
Matrix.setIdentityM(MATRIX_IDENTITY, 0);
}
/**
* The transition time, in milliseconds, for Night Display to turn on/off.
*/
private static final long TRANSITION_DURATION = 3000L;
private static final int MSG_USER_CHANGED = 0;
private static final int MSG_SET_UP = 1;
private static final int MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE = 2;
private static final int MSG_APPLY_NIGHT_DISPLAY_ANIMATED = 3;
private static final int MSG_APPLY_GLOBAL_SATURATION = 4;
private static final int MSG_APPLY_DISPLAY_WHITE_BALANCE = 5;
/**
* Return value if a setting has not been set.
*/
private static final int NOT_SET = -1;
/**
* Evaluator used to animate color matrix transitions.
*/
private static final ColorMatrixEvaluator COLOR_MATRIX_EVALUATOR = new ColorMatrixEvaluator();
private final NightDisplayTintController mNightDisplayTintController =
new NightDisplayTintController();
@VisibleForTesting
final DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController =
new DisplayWhiteBalanceTintController();
private final TintController mGlobalSaturationTintController =
new GlobalSaturationTintController();
/**
* Matrix and offset used for converting color to grayscale.
*/
private static final float[] MATRIX_GRAYSCALE = new float[]{
.2126f, .2126f, .2126f, 0f,
.7152f, .7152f, .7152f, 0f,
.0722f, .0722f, .0722f, 0f,
0f, 0f, 0f, 1f
};
/**
* Matrix and offset used for luminance inversion. Represents a transform from RGB to YIQ color
* space, rotation around the Y axis by 180 degrees, transform back to RGB color space, and
* subtraction from 1. The last row represents a non-multiplied addition, see surfaceflinger's
* ProgramCache for full implementation details.
*/
private static final float[] MATRIX_INVERT_COLOR = new float[]{
0.402f, -0.598f, -0.599f, 0f,
-1.174f, -0.174f, -1.175f, 0f,
-0.228f, -0.228f, 0.772f, 0f,
1f, 1f, 1f, 1f
};
private final Handler mHandler;
private final AppSaturationController mAppSaturationController = new AppSaturationController();
private int mCurrentUser = UserHandle.USER_NULL;
private ContentObserver mUserSetupObserver;
private boolean mBootCompleted;
private ContentObserver mContentObserver;
private DisplayWhiteBalanceListener mDisplayWhiteBalanceListener;
private NightDisplayAutoMode mNightDisplayAutoMode;
/**
* Map of color modes -> display composition colorspace
*/
private SparseIntArray mColorModeCompositionColorSpaces = null;
public ColorDisplayService(Context context) {
super(context);
mHandler = new TintHandler(DisplayThread.get().getLooper());
}
@Override
public void onStart() {
publishBinderService(Context.COLOR_DISPLAY_SERVICE, new BinderService());
publishLocalService(ColorDisplayServiceInternal.class, new ColorDisplayServiceInternal());
publishLocalService(DisplayTransformManager.class, new DisplayTransformManager());
}
@Override
public void onBootPhase(int phase) {
if (phase >= PHASE_BOOT_COMPLETED) {
mBootCompleted = true;
// Register listeners now that boot is complete.
if (mCurrentUser != UserHandle.USER_NULL && mUserSetupObserver == null) {
mHandler.sendEmptyMessage(MSG_SET_UP);
}
}
}
@Override
public void onStartUser(int userHandle) {
super.onStartUser(userHandle);
if (mCurrentUser == UserHandle.USER_NULL) {
final Message message = mHandler.obtainMessage(MSG_USER_CHANGED);
message.arg1 = userHandle;
mHandler.sendMessage(message);
}
}
@Override
public void onSwitchUser(int userHandle) {
super.onSwitchUser(userHandle);
final Message message = mHandler.obtainMessage(MSG_USER_CHANGED);
message.arg1 = userHandle;
mHandler.sendMessage(message);
}
@Override
public void onStopUser(int userHandle) {
super.onStopUser(userHandle);
if (mCurrentUser == userHandle) {
final Message message = mHandler.obtainMessage(MSG_USER_CHANGED);
message.arg1 = UserHandle.USER_NULL;
mHandler.sendMessage(message);
}
}
@VisibleForTesting void onUserChanged(int userHandle) {
final ContentResolver cr = getContext().getContentResolver();
if (mCurrentUser != UserHandle.USER_NULL) {
if (mUserSetupObserver != null) {
cr.unregisterContentObserver(mUserSetupObserver);
mUserSetupObserver = null;
} else if (mBootCompleted) {
tearDown();
}
}
mCurrentUser = userHandle;
if (mCurrentUser != UserHandle.USER_NULL) {
if (!isUserSetupCompleted(cr, mCurrentUser)) {
mUserSetupObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange, Uri uri) {
if (isUserSetupCompleted(cr, mCurrentUser)) {
cr.unregisterContentObserver(this);
mUserSetupObserver = null;
if (mBootCompleted) {
setUp();
}
}
}
};
cr.registerContentObserver(Secure.getUriFor(Secure.USER_SETUP_COMPLETE),
false /* notifyForDescendants */, mUserSetupObserver, mCurrentUser);
} else if (mBootCompleted) {
setUp();
}
}
}
private static boolean isUserSetupCompleted(ContentResolver cr, int userHandle) {
return Secure.getIntForUser(cr, Secure.USER_SETUP_COMPLETE, 0, userHandle) == 1;
}
private void setUpDisplayCompositionColorSpaces(Resources res) {
mColorModeCompositionColorSpaces = null;
final int[] colorModes = res.getIntArray(R.array.config_displayCompositionColorModes);
if (colorModes == null) {
return;
}
final int[] compSpaces = res.getIntArray(R.array.config_displayCompositionColorSpaces);
if (compSpaces == null) {
return;
}
if (colorModes.length != compSpaces.length) {
Slog.e(TAG, "Number of composition color spaces doesn't match specified color modes");
return;
}
mColorModeCompositionColorSpaces = new SparseIntArray(colorModes.length);
for (int i = 0; i < colorModes.length; i++) {
mColorModeCompositionColorSpaces.put(colorModes[i], compSpaces[i]);
}
}
private void setUp() {
Slog.d(TAG, "setUp: currentUser=" + mCurrentUser);
// Listen for external changes to any of the settings.
if (mContentObserver == null) {
mContentObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
final String setting = uri == null ? null : uri.getLastPathSegment();
if (setting != null) {
switch (setting) {
case Secure.NIGHT_DISPLAY_ACTIVATED:
final boolean activated = mNightDisplayTintController
.isActivatedSetting();
if (mNightDisplayTintController.isActivatedStateNotSet()
|| mNightDisplayTintController.isActivated() != activated) {
mNightDisplayTintController.setActivated(activated);
}
break;
case Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE:
final int temperature = mNightDisplayTintController
.getColorTemperatureSetting();
if (mNightDisplayTintController.getColorTemperature()
!= temperature) {
mNightDisplayTintController
.onColorTemperatureChanged(temperature);
}
break;
case Secure.NIGHT_DISPLAY_AUTO_MODE:
onNightDisplayAutoModeChanged(getNightDisplayAutoModeInternal());
break;
case Secure.NIGHT_DISPLAY_CUSTOM_START_TIME:
onNightDisplayCustomStartTimeChanged(
getNightDisplayCustomStartTimeInternal().getLocalTime());
break;
case Secure.NIGHT_DISPLAY_CUSTOM_END_TIME:
onNightDisplayCustomEndTimeChanged(
getNightDisplayCustomEndTimeInternal().getLocalTime());
break;
case System.DISPLAY_COLOR_MODE:
onDisplayColorModeChanged(getColorModeInternal());
break;
case Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED:
onAccessibilityInversionChanged();
onAccessibilityActivated();
break;
case Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED:
onAccessibilityDaltonizerChanged();
onAccessibilityActivated();
break;
case Secure.ACCESSIBILITY_DISPLAY_DALTONIZER:
onAccessibilityDaltonizerChanged();
break;
case Secure.DISPLAY_WHITE_BALANCE_ENABLED:
updateDisplayWhiteBalanceStatus();
break;
}
}
}
};
}
final ContentResolver cr = getContext().getContentResolver();
cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_ACTIVATED),
false /* notifyForDescendants */, mContentObserver, mCurrentUser);
cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE),
false /* notifyForDescendants */, mContentObserver, mCurrentUser);
cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_AUTO_MODE),
false /* notifyForDescendants */, mContentObserver, mCurrentUser);
cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_CUSTOM_START_TIME),
false /* notifyForDescendants */, mContentObserver, mCurrentUser);
cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_CUSTOM_END_TIME),
false /* notifyForDescendants */, mContentObserver, mCurrentUser);
cr.registerContentObserver(System.getUriFor(System.DISPLAY_COLOR_MODE),
false /* notifyForDescendants */, mContentObserver, mCurrentUser);
cr.registerContentObserver(
Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED),
false /* notifyForDescendants */, mContentObserver, mCurrentUser);
cr.registerContentObserver(
Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED),
false /* notifyForDescendants */, mContentObserver, mCurrentUser);
cr.registerContentObserver(
Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER),
false /* notifyForDescendants */, mContentObserver, mCurrentUser);
cr.registerContentObserver(Secure.getUriFor(Secure.DISPLAY_WHITE_BALANCE_ENABLED),
false /* notifyForDescendants */, mContentObserver, mCurrentUser);
// Apply the accessibility settings first, since they override most other settings.
onAccessibilityInversionChanged();
onAccessibilityDaltonizerChanged();
setUpDisplayCompositionColorSpaces(getContext().getResources());
// Set the color mode, if valid, and immediately apply the updated tint matrix based on the
// existing activated state. This ensures consistency of tint across the color mode change.
onDisplayColorModeChanged(getColorModeInternal());
if (mNightDisplayTintController.isAvailable(getContext())) {
// Reset the activated state.
mNightDisplayTintController.setActivated(null);
// Prepare the night display color transformation matrix.
mNightDisplayTintController
.setUp(getContext(), DisplayTransformManager.needsLinearColorMatrix());
mNightDisplayTintController
.setMatrix(mNightDisplayTintController.getColorTemperatureSetting());
// Initialize the current auto mode.
onNightDisplayAutoModeChanged(getNightDisplayAutoModeInternal());
// Force the initialization of the current saved activation state.
if (mNightDisplayTintController.isActivatedStateNotSet()) {
mNightDisplayTintController
.setActivated(mNightDisplayTintController.isActivatedSetting());
}
}
if (mDisplayWhiteBalanceTintController.isAvailable(getContext())) {
// Prepare the display white balance transform matrix.
mDisplayWhiteBalanceTintController.setUp(getContext(), true /* needsLinear */);
updateDisplayWhiteBalanceStatus();
}
}
private void tearDown() {
Slog.d(TAG, "tearDown: currentUser=" + mCurrentUser);
if (mContentObserver != null) {
getContext().getContentResolver().unregisterContentObserver(mContentObserver);
}
if (mNightDisplayTintController.isAvailable(getContext())) {
if (mNightDisplayAutoMode != null) {
mNightDisplayAutoMode.onStop();
mNightDisplayAutoMode = null;
}
mNightDisplayTintController.endAnimator();
}
if (mDisplayWhiteBalanceTintController.isAvailable(getContext())) {
mDisplayWhiteBalanceTintController.endAnimator();
}
if (mGlobalSaturationTintController.isAvailable(getContext())) {
mGlobalSaturationTintController.setActivated(null);
}
}
private void onNightDisplayAutoModeChanged(int autoMode) {
Slog.d(TAG, "onNightDisplayAutoModeChanged: autoMode=" + autoMode);
if (mNightDisplayAutoMode != null) {
mNightDisplayAutoMode.onStop();
mNightDisplayAutoMode = null;
}
if (autoMode == AUTO_MODE_CUSTOM_TIME) {
mNightDisplayAutoMode = new CustomNightDisplayAutoMode();
} else if (autoMode == AUTO_MODE_TWILIGHT) {
mNightDisplayAutoMode = new TwilightNightDisplayAutoMode();
}
if (mNightDisplayAutoMode != null) {
mNightDisplayAutoMode.onStart();
}
}
private void onNightDisplayCustomStartTimeChanged(LocalTime startTime) {
Slog.d(TAG, "onNightDisplayCustomStartTimeChanged: startTime=" + startTime);
if (mNightDisplayAutoMode != null) {
mNightDisplayAutoMode.onCustomStartTimeChanged(startTime);
}
}
private void onNightDisplayCustomEndTimeChanged(LocalTime endTime) {
Slog.d(TAG, "onNightDisplayCustomEndTimeChanged: endTime=" + endTime);
if (mNightDisplayAutoMode != null) {
mNightDisplayAutoMode.onCustomEndTimeChanged(endTime);
}
}
private int getCompositionColorSpace(int mode) {
if (mColorModeCompositionColorSpaces == null) {
return Display.COLOR_MODE_INVALID;
}
return mColorModeCompositionColorSpaces.get(mode, Display.COLOR_MODE_INVALID);
}
private void onDisplayColorModeChanged(int mode) {
if (mode == NOT_SET) {
return;
}
mNightDisplayTintController.cancelAnimator();
mDisplayWhiteBalanceTintController.cancelAnimator();
if (mNightDisplayTintController.isAvailable(getContext())) {
mNightDisplayTintController
.setUp(getContext(), DisplayTransformManager.needsLinearColorMatrix(mode));
mNightDisplayTintController
.setMatrix(mNightDisplayTintController.getColorTemperatureSetting());
}
// dtm.setColorMode() needs to be called before
// updateDisplayWhiteBalanceStatus(), this is because the latter calls
// DisplayTransformManager.needsLinearColorMatrix(), therefore it is dependent
// on the state of DisplayTransformManager.
final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
dtm.setColorMode(mode, mNightDisplayTintController.getMatrix(),
getCompositionColorSpace(mode));
if (mDisplayWhiteBalanceTintController.isAvailable(getContext())) {
updateDisplayWhiteBalanceStatus();
}
}
private void onAccessibilityActivated() {
onDisplayColorModeChanged(getColorModeInternal());
}
private boolean isAccessiblityDaltonizerEnabled() {
return Secure.getIntForUser(getContext().getContentResolver(),
Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0, mCurrentUser) != 0;
}
private boolean isAccessiblityInversionEnabled() {
return Secure.getIntForUser(getContext().getContentResolver(),
Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0, mCurrentUser) != 0;
}
private boolean isAccessibilityEnabled() {
return isAccessiblityDaltonizerEnabled() || isAccessiblityInversionEnabled();
}
/**
* Apply the accessibility daltonizer transform based on the settings value.
*/
private void onAccessibilityDaltonizerChanged() {
if (mCurrentUser == UserHandle.USER_NULL) {
return;
}
final int daltonizerMode = isAccessiblityDaltonizerEnabled()
? Secure.getIntForUser(getContext().getContentResolver(),
Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY, mCurrentUser)
: AccessibilityManager.DALTONIZER_DISABLED;
final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
if (daltonizerMode == AccessibilityManager.DALTONIZER_SIMULATE_MONOCHROMACY) {
// Monochromacy isn't supported by the native Daltonizer implementation; use grayscale.
dtm.setColorMatrix(DisplayTransformManager.LEVEL_COLOR_MATRIX_GRAYSCALE,
MATRIX_GRAYSCALE);
dtm.setDaltonizerMode(AccessibilityManager.DALTONIZER_DISABLED);
} else {
dtm.setColorMatrix(DisplayTransformManager.LEVEL_COLOR_MATRIX_GRAYSCALE, null);
dtm.setDaltonizerMode(daltonizerMode);
}
}
/**
* Apply the accessibility inversion transform based on the settings value.
*/
private void onAccessibilityInversionChanged() {
if (mCurrentUser == UserHandle.USER_NULL) {
return;
}
final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
dtm.setColorMatrix(DisplayTransformManager.LEVEL_COLOR_MATRIX_INVERT_COLOR,
isAccessiblityInversionEnabled() ? MATRIX_INVERT_COLOR : null);
}
/**
* Applies current color temperature matrix, or removes it if deactivated.
*
* @param immediate {@code true} skips transition animation
*/
private void applyTint(TintController tintController, boolean immediate) {
tintController.cancelAnimator();
final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
final float[] from = dtm.getColorMatrix(tintController.getLevel());
final float[] to = tintController.getMatrix();
if (immediate) {
dtm.setColorMatrix(tintController.getLevel(), to);
} else {
tintController.setAnimator(ValueAnimator.ofObject(COLOR_MATRIX_EVALUATOR,
from == null ? MATRIX_IDENTITY : from, to));
tintController.getAnimator().setDuration(TRANSITION_DURATION);
tintController.getAnimator().setInterpolator(AnimationUtils.loadInterpolator(
getContext(), android.R.interpolator.fast_out_slow_in));
tintController.getAnimator().addUpdateListener((ValueAnimator animator) -> {
final float[] value = (float[]) animator.getAnimatedValue();
dtm.setColorMatrix(tintController.getLevel(), value);
});
tintController.getAnimator().addListener(new AnimatorListenerAdapter() {
private boolean mIsCancelled;
@Override
public void onAnimationCancel(Animator animator) {
mIsCancelled = true;
}
@Override
public void onAnimationEnd(Animator animator) {
if (!mIsCancelled) {
// Ensure final color matrix is set at the end of the animation. If the
// animation is cancelled then don't set the final color matrix so the new
// animator can pick up from where this one left off.
dtm.setColorMatrix(tintController.getLevel(), to);
}
tintController.setAnimator(null);
}
});
tintController.getAnimator().start();
}
}
/**
* Returns the first date time corresponding to the local time that occurs before the provided
* date time.
*
* @param compareTime the LocalDateTime to compare against
* @return the prior LocalDateTime corresponding to this local time
*/
@VisibleForTesting
static LocalDateTime getDateTimeBefore(LocalTime localTime, LocalDateTime compareTime) {
final LocalDateTime ldt = LocalDateTime.of(compareTime.getYear(), compareTime.getMonth(),
compareTime.getDayOfMonth(), localTime.getHour(), localTime.getMinute());
// Check if the local time has passed, if so return the same time yesterday.
return ldt.isAfter(compareTime) ? ldt.minusDays(1) : ldt;
}
/**
* Returns the first date time corresponding to this local time that occurs after the provided
* date time.
*
* @param compareTime the LocalDateTime to compare against
* @return the next LocalDateTime corresponding to this local time
*/
@VisibleForTesting
static LocalDateTime getDateTimeAfter(LocalTime localTime, LocalDateTime compareTime) {
final LocalDateTime ldt = LocalDateTime.of(compareTime.getYear(), compareTime.getMonth(),
compareTime.getDayOfMonth(), localTime.getHour(), localTime.getMinute());
// Check if the local time has passed, if so return the same time tomorrow.
return ldt.isBefore(compareTime) ? ldt.plusDays(1) : ldt;
}
@VisibleForTesting
void updateDisplayWhiteBalanceStatus() {
boolean oldActivated = mDisplayWhiteBalanceTintController.isActivated();
mDisplayWhiteBalanceTintController.setActivated(isDisplayWhiteBalanceSettingEnabled()
&& !mNightDisplayTintController.isActivated()
&& !isAccessibilityEnabled()
&& DisplayTransformManager.needsLinearColorMatrix());
boolean activated = mDisplayWhiteBalanceTintController.isActivated();
if (mDisplayWhiteBalanceListener != null && oldActivated != activated) {
mDisplayWhiteBalanceListener.onDisplayWhiteBalanceStatusChanged(activated);
}
// If disabled, clear the tint. If enabled, do nothing more here and let the next
// temperature update set the correct tint.
if (!activated) {
mHandler.sendEmptyMessage(MSG_APPLY_DISPLAY_WHITE_BALANCE);
}
}
private boolean setDisplayWhiteBalanceSettingEnabled(boolean enabled) {
if (mCurrentUser == UserHandle.USER_NULL) {
return false;
}
return Secure.putIntForUser(getContext().getContentResolver(),
Secure.DISPLAY_WHITE_BALANCE_ENABLED,
enabled ? 1 : 0, mCurrentUser);
}
private boolean isDisplayWhiteBalanceSettingEnabled() {
if (mCurrentUser == UserHandle.USER_NULL) {
return false;
}
return Secure.getIntForUser(getContext().getContentResolver(),
Secure.DISPLAY_WHITE_BALANCE_ENABLED,
getContext().getResources()
.getBoolean(R.bool.config_displayWhiteBalanceEnabledDefault) ? 1
: 0,
mCurrentUser) == 1;
}
private boolean isDeviceColorManagedInternal() {
final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
return dtm.isDeviceColorManaged();
}
private int getTransformCapabilitiesInternal() {
int availabilityFlags = ColorDisplayManager.CAPABILITY_NONE;
if (SurfaceControl.getProtectedContentSupport()) {
availabilityFlags |= ColorDisplayManager.CAPABILITY_PROTECTED_CONTENT;
}
final Resources res = getContext().getResources();
if (res.getBoolean(R.bool.config_setColorTransformAccelerated)) {
availabilityFlags |= ColorDisplayManager.CAPABILITY_HARDWARE_ACCELERATION_GLOBAL;
}
if (res.getBoolean(R.bool.config_setColorTransformAcceleratedPerLayer)) {
availabilityFlags |= ColorDisplayManager.CAPABILITY_HARDWARE_ACCELERATION_PER_APP;
}
return availabilityFlags;
}
private boolean setNightDisplayAutoModeInternal(@AutoMode int autoMode) {
if (getNightDisplayAutoModeInternal() != autoMode) {
Secure.putStringForUser(getContext().getContentResolver(),
Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
null,
mCurrentUser);
}
return Secure.putIntForUser(getContext().getContentResolver(),
Secure.NIGHT_DISPLAY_AUTO_MODE, autoMode, mCurrentUser);
}
private int getNightDisplayAutoModeInternal() {
int autoMode = getNightDisplayAutoModeRawInternal();
if (autoMode == NOT_SET) {
autoMode = getContext().getResources().getInteger(
R.integer.config_defaultNightDisplayAutoMode);
}
if (autoMode != AUTO_MODE_DISABLED
&& autoMode != AUTO_MODE_CUSTOM_TIME
&& autoMode != AUTO_MODE_TWILIGHT) {
Slog.e(TAG, "Invalid autoMode: " + autoMode);
autoMode = AUTO_MODE_DISABLED;
}
return autoMode;
}
private int getNightDisplayAutoModeRawInternal() {
if (mCurrentUser == UserHandle.USER_NULL) {
return NOT_SET;
}
return Secure
.getIntForUser(getContext().getContentResolver(), Secure.NIGHT_DISPLAY_AUTO_MODE,
NOT_SET, mCurrentUser);
}
private Time getNightDisplayCustomStartTimeInternal() {
int startTimeValue = Secure.getIntForUser(getContext().getContentResolver(),
Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, NOT_SET, mCurrentUser);
if (startTimeValue == NOT_SET) {
startTimeValue = getContext().getResources().getInteger(
R.integer.config_defaultNightDisplayCustomStartTime);
}
return new Time(LocalTime.ofSecondOfDay(startTimeValue / 1000));
}
private boolean setNightDisplayCustomStartTimeInternal(Time startTime) {
return Secure.putIntForUser(getContext().getContentResolver(),
Secure.NIGHT_DISPLAY_CUSTOM_START_TIME,
startTime.getLocalTime().toSecondOfDay() * 1000,
mCurrentUser);
}
private Time getNightDisplayCustomEndTimeInternal() {
int endTimeValue = Secure.getIntForUser(getContext().getContentResolver(),
Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, NOT_SET, mCurrentUser);
if (endTimeValue == NOT_SET) {
endTimeValue = getContext().getResources().getInteger(
R.integer.config_defaultNightDisplayCustomEndTime);
}
return new Time(LocalTime.ofSecondOfDay(endTimeValue / 1000));
}
private boolean setNightDisplayCustomEndTimeInternal(Time endTime) {
return Secure.putIntForUser(getContext().getContentResolver(),
Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, endTime.getLocalTime().toSecondOfDay() * 1000,
mCurrentUser);
}
/**
* Returns the last time the night display transform activation state was changed, or {@link
* LocalDateTime#MIN} if night display has never been activated.
*/
private LocalDateTime getNightDisplayLastActivatedTimeSetting() {
final ContentResolver cr = getContext().getContentResolver();
final String lastActivatedTime = Secure.getStringForUser(
cr, Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, getContext().getUserId());
if (lastActivatedTime != null) {
try {
return LocalDateTime.parse(lastActivatedTime);
} catch (DateTimeParseException ignored) {
}
// Uses the old epoch time.
try {
return LocalDateTime.ofInstant(
Instant.ofEpochMilli(Long.parseLong(lastActivatedTime)),
ZoneId.systemDefault());
} catch (DateTimeException | NumberFormatException ignored) {
}
}
return LocalDateTime.MIN;
}
private boolean setAppSaturationLevelInternal(String packageName, int saturationLevel) {
return mAppSaturationController
.setSaturationLevel(packageName, mCurrentUser, saturationLevel);
}
private void setColorModeInternal(@ColorMode int colorMode) {
if (!isColorModeAvailable(colorMode)) {
throw new IllegalArgumentException("Invalid colorMode: " + colorMode);
}
System.putIntForUser(getContext().getContentResolver(), System.DISPLAY_COLOR_MODE,
colorMode,
mCurrentUser);
}
private @ColorMode int getColorModeInternal() {
final ContentResolver cr = getContext().getContentResolver();
if (isAccessibilityEnabled()) {
// There are restrictions on the available color modes combined with a11y transforms.
final int a11yColorMode = getContext().getResources().getInteger(
R.integer.config_accessibilityColorMode);
if (a11yColorMode >= 0) {
return a11yColorMode;
}
}
int colorMode = System.getIntForUser(cr, System.DISPLAY_COLOR_MODE, -1, mCurrentUser);
if (colorMode == -1) {
// There might be a system property controlling color mode that we need to respect; if
// not, this will set a suitable default.
colorMode = getCurrentColorModeFromSystemProperties();
}
// This happens when a color mode is no longer available (e.g., after system update or B&R)
// or the device does not support any color mode.
if (!isColorModeAvailable(colorMode)) {
if (colorMode == COLOR_MODE_BOOSTED && isColorModeAvailable(COLOR_MODE_NATURAL)) {
colorMode = COLOR_MODE_NATURAL;
} else if (colorMode == COLOR_MODE_SATURATED
&& isColorModeAvailable(COLOR_MODE_AUTOMATIC)) {
colorMode = COLOR_MODE_AUTOMATIC;
} else if (colorMode == COLOR_MODE_AUTOMATIC
&& isColorModeAvailable(COLOR_MODE_SATURATED)) {
colorMode = COLOR_MODE_SATURATED;
} else {
colorMode = -1;
}
}
return colorMode;
}
/**
* Get the current color mode from system properties, or return -1 if invalid.
*
* See {@link DisplayTransformManager}
*/
private @ColorMode int getCurrentColorModeFromSystemProperties() {
final int displayColorSetting = SystemProperties.getInt("persist.sys.sf.native_mode", 0);
if (displayColorSetting == 0) {
return "1.0".equals(SystemProperties.get("persist.sys.sf.color_saturation"))
? COLOR_MODE_NATURAL : COLOR_MODE_BOOSTED;
} else if (displayColorSetting == 1) {
return COLOR_MODE_SATURATED;
} else if (displayColorSetting == 2) {
return COLOR_MODE_AUTOMATIC;
} else if (displayColorSetting >= VENDOR_COLOR_MODE_RANGE_MIN
&& displayColorSetting <= VENDOR_COLOR_MODE_RANGE_MAX) {
return displayColorSetting;
} else {
return -1;
}
}
private boolean isColorModeAvailable(@ColorMode int colorMode) {
final int[] availableColorModes = getContext().getResources().getIntArray(
R.array.config_availableColorModes);
if (availableColorModes != null) {
for (int mode : availableColorModes) {
if (mode == colorMode) {
return true;
}
}
}
return false;
}
private void dumpInternal(PrintWriter pw) {
pw.println("COLOR DISPLAY MANAGER dumpsys (color_display)");
pw.println("Night display:");
if (mNightDisplayTintController.isAvailable(getContext())) {
pw.println(" Activated: " + mNightDisplayTintController.isActivated());
pw.println(" Color temp: " + mNightDisplayTintController.getColorTemperature());
} else {
pw.println(" Not available");
}
pw.println("Global saturation:");
if (mGlobalSaturationTintController.isAvailable(getContext())) {
pw.println(" Activated: " + mGlobalSaturationTintController.isActivated());
} else {
pw.println(" Not available");
}
mAppSaturationController.dump(pw);
pw.println("Display white balance:");
if (mDisplayWhiteBalanceTintController.isAvailable(getContext())) {
pw.println(" Activated: " + mDisplayWhiteBalanceTintController.isActivated());
mDisplayWhiteBalanceTintController.dump(pw);
} else {
pw.println(" Not available");
}
pw.println("Color mode: " + getColorModeInternal());
}
private abstract class NightDisplayAutoMode {
public abstract void onActivated(boolean activated);
public abstract void onStart();
public abstract void onStop();
public void onCustomStartTimeChanged(LocalTime startTime) {
}
public void onCustomEndTimeChanged(LocalTime endTime) {
}
}
private final class CustomNightDisplayAutoMode extends NightDisplayAutoMode implements
AlarmManager.OnAlarmListener {
private final AlarmManager mAlarmManager;
private final BroadcastReceiver mTimeChangedReceiver;
private LocalTime mStartTime;
private LocalTime mEndTime;
private LocalDateTime mLastActivatedTime;
CustomNightDisplayAutoMode() {
mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
mTimeChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
updateActivated();
}
};
}
private void updateActivated() {
final LocalDateTime now = LocalDateTime.now();
final LocalDateTime start = getDateTimeBefore(mStartTime, now);
final LocalDateTime end = getDateTimeAfter(mEndTime, start);
boolean activate = now.isBefore(end);
if (mLastActivatedTime != null) {
// Maintain the existing activated state if within the current period.
if (mLastActivatedTime.isBefore(now)
&& mLastActivatedTime.isAfter(start)
&& (mLastActivatedTime.isAfter(end) || now.isBefore(end))) {
activate = mNightDisplayTintController.isActivatedSetting();
}
}
if (mNightDisplayTintController.isActivatedStateNotSet()
|| (mNightDisplayTintController.isActivated() != activate)) {
mNightDisplayTintController.setActivated(activate, activate ? start : end);
}
updateNextAlarm(mNightDisplayTintController.isActivated(), now);
}
private void updateNextAlarm(@Nullable Boolean activated, @NonNull LocalDateTime now) {
if (activated != null) {
final LocalDateTime next = activated ? getDateTimeAfter(mEndTime, now)
: getDateTimeAfter(mStartTime, now);
final long millis = next.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
mAlarmManager.setExact(AlarmManager.RTC, millis, TAG, this, null);
}
}
@Override
public void onStart() {
final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_TIME_CHANGED);
intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
getContext().registerReceiver(mTimeChangedReceiver, intentFilter);
mStartTime = getNightDisplayCustomStartTimeInternal().getLocalTime();
mEndTime = getNightDisplayCustomEndTimeInternal().getLocalTime();
mLastActivatedTime = getNightDisplayLastActivatedTimeSetting();
// Force an update to initialize state.
updateActivated();
}
@Override
public void onStop() {
getContext().unregisterReceiver(mTimeChangedReceiver);
mAlarmManager.cancel(this);
mLastActivatedTime = null;
}
@Override
public void onActivated(boolean activated) {
mLastActivatedTime = getNightDisplayLastActivatedTimeSetting();
updateNextAlarm(activated, LocalDateTime.now());
}
@Override
public void onCustomStartTimeChanged(LocalTime startTime) {
mStartTime = startTime;
mLastActivatedTime = null;
updateActivated();
}
@Override
public void onCustomEndTimeChanged(LocalTime endTime) {
mEndTime = endTime;
mLastActivatedTime = null;
updateActivated();
}
@Override
public void onAlarm() {
Slog.d(TAG, "onAlarm");
updateActivated();
}
}
private final class TwilightNightDisplayAutoMode extends NightDisplayAutoMode implements
TwilightListener {
private final TwilightManager mTwilightManager;
private LocalDateTime mLastActivatedTime;
TwilightNightDisplayAutoMode() {
mTwilightManager = getLocalService(TwilightManager.class);
}
private void updateActivated(TwilightState state) {
if (state == null) {
// If there isn't a valid TwilightState then just keep the current activated
// state.
return;
}
boolean activate = state.isNight();
if (mLastActivatedTime != null) {
final LocalDateTime now = LocalDateTime.now();
final LocalDateTime sunrise = state.sunrise();
final LocalDateTime sunset = state.sunset();
// Maintain the existing activated state if within the current period.
if (mLastActivatedTime.isBefore(now) && (mLastActivatedTime.isBefore(sunrise)
^ mLastActivatedTime.isBefore(sunset))) {
activate = mNightDisplayTintController.isActivatedSetting();
}
}
if (mNightDisplayTintController.isActivatedStateNotSet() || (
mNightDisplayTintController.isActivated() != activate)) {
mNightDisplayTintController.setActivated(activate);
}
}
@Override
public void onActivated(boolean activated) {
mLastActivatedTime = getNightDisplayLastActivatedTimeSetting();
}
@Override
public void onStart() {
mTwilightManager.registerListener(this, mHandler);
mLastActivatedTime = getNightDisplayLastActivatedTimeSetting();
// Force an update to initialize state.
updateActivated(mTwilightManager.getLastTwilightState());
}
@Override
public void onStop() {
mTwilightManager.unregisterListener(this);
mLastActivatedTime = null;
}
@Override
public void onTwilightStateChanged(@Nullable TwilightState state) {
Slog.d(TAG, "onTwilightStateChanged: isNight="
+ (state == null ? null : state.isNight()));
updateActivated(state);
}
}
/**
* Interpolates between two 4x4 color transform matrices (in column-major order).
*/
private static class ColorMatrixEvaluator implements TypeEvaluator<float[]> {
/**
* Result matrix returned by {@link #evaluate(float, float[], float[])}.
*/
private final float[] mResultMatrix = new float[16];
@Override
public float[] evaluate(float fraction, float[] startValue, float[] endValue) {
for (int i = 0; i < mResultMatrix.length; i++) {
mResultMatrix[i] = MathUtils.lerp(startValue[i], endValue[i], fraction);
}
return mResultMatrix;
}
}
private final class NightDisplayTintController extends TintController {
private final float[] mMatrix = new float[16];
private final float[] mColorTempCoefficients = new float[9];
private Boolean mIsAvailable;
private Integer mColorTemp;
/**
* Set coefficients based on whether the color matrix is linear or not.
*/
@Override
public void setUp(Context context, boolean needsLinear) {
final String[] coefficients = context.getResources().getStringArray(needsLinear
? R.array.config_nightDisplayColorTemperatureCoefficients
: R.array.config_nightDisplayColorTemperatureCoefficientsNative);
for (int i = 0; i < 9 && i < coefficients.length; i++) {
mColorTempCoefficients[i] = Float.parseFloat(coefficients[i]);
}
}
@Override
public void setMatrix(int cct) {
if (mMatrix.length != 16) {
Slog.d(TAG, "The display transformation matrix must be 4x4");
return;
}
Matrix.setIdentityM(mMatrix, 0);
final float squareTemperature = cct * cct;
final float red = squareTemperature * mColorTempCoefficients[0]
+ cct * mColorTempCoefficients[1] + mColorTempCoefficients[2];
final float green = squareTemperature * mColorTempCoefficients[3]
+ cct * mColorTempCoefficients[4] + mColorTempCoefficients[5];
final float blue = squareTemperature * mColorTempCoefficients[6]
+ cct * mColorTempCoefficients[7] + mColorTempCoefficients[8];
mMatrix[0] = red;
mMatrix[5] = green;
mMatrix[10] = blue;
}
@Override
public float[] getMatrix() {
return isActivated() ? mMatrix : MATRIX_IDENTITY;
}
@Override
public void setActivated(Boolean activated) {
setActivated(activated, LocalDateTime.now());
}
/**
* Use directly when it is important that the last activation time be exact (for example, an
* automatic change). Otherwise use {@link #setActivated(Boolean)}.
*/
public void setActivated(Boolean activated, @NonNull LocalDateTime lastActivationTime) {
if (activated == null) {
super.setActivated(null);
return;
}
boolean activationStateChanged = activated != isActivated();
if (!isActivatedStateNotSet() && activationStateChanged) {
// This is a true state change, so set this as the last activation time.
Secure.putStringForUser(getContext().getContentResolver(),
Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
lastActivationTime.toString(),
mCurrentUser);
}
if (isActivatedStateNotSet() || activationStateChanged) {
super.setActivated(activated);
if (isActivatedSetting() != activated) {
Secure.putIntForUser(getContext().getContentResolver(),
Secure.NIGHT_DISPLAY_ACTIVATED,
activated ? 1 : 0, mCurrentUser);
}
onActivated(activated);
}
}
@Override
public int getLevel() {
return LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
}
@Override
public boolean isAvailable(Context context) {
if (mIsAvailable == null) {
mIsAvailable = ColorDisplayManager.isNightDisplayAvailable(context);
}
return mIsAvailable;
}
private void onActivated(boolean activated) {
Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display");
if (mNightDisplayAutoMode != null) {
mNightDisplayAutoMode.onActivated(activated);
}
if (mDisplayWhiteBalanceTintController.isAvailable(getContext())) {
updateDisplayWhiteBalanceStatus();
}
mHandler.sendEmptyMessage(MSG_APPLY_NIGHT_DISPLAY_ANIMATED);
}
int getColorTemperature() {
return mColorTemp != null ? clampNightDisplayColorTemperature(mColorTemp)
: getColorTemperatureSetting();
}
boolean setColorTemperature(int temperature) {
mColorTemp = temperature;
final boolean success = Secure.putIntForUser(getContext().getContentResolver(),
Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, temperature, mCurrentUser);
onColorTemperatureChanged(temperature);
return success;
}
void onColorTemperatureChanged(int temperature) {
setMatrix(temperature);
mHandler.sendEmptyMessage(MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE);
}
boolean isActivatedSetting() {
if (mCurrentUser == UserHandle.USER_NULL) {
return false;
}
return Secure.getIntForUser(getContext().getContentResolver(),
Secure.NIGHT_DISPLAY_ACTIVATED, 0, mCurrentUser) == 1;
}
int getColorTemperatureSetting() {
if (mCurrentUser == UserHandle.USER_NULL) {
return NOT_SET;
}
return clampNightDisplayColorTemperature(Secure.getIntForUser(
getContext().getContentResolver(), Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE,
NOT_SET,
mCurrentUser));
}
private int clampNightDisplayColorTemperature(int colorTemperature) {
if (colorTemperature == NOT_SET) {
colorTemperature = getContext().getResources().getInteger(
R.integer.config_nightDisplayColorTemperatureDefault);
}
final int minimumTemperature = ColorDisplayManager
.getMinimumColorTemperature(getContext());
final int maximumTemperature = ColorDisplayManager
.getMaximumColorTemperature(getContext());
if (colorTemperature < minimumTemperature) {
colorTemperature = minimumTemperature;
} else if (colorTemperature > maximumTemperature) {
colorTemperature = maximumTemperature;
}
return colorTemperature;
}
}
/**
* Local service that allows color transforms to be enabled from other system services.
*/
public final class ColorDisplayServiceInternal {
/**
* Set the current CCT value for the display white balance transform, and if the transform
* is enabled, apply it.
*
* @param cct the color temperature in Kelvin.
*/
public boolean setDisplayWhiteBalanceColorTemperature(int cct) {
// Update the transform matrix even if it can't be applied.
mDisplayWhiteBalanceTintController.setMatrix(cct);
if (mDisplayWhiteBalanceTintController.isActivated()) {
mHandler.sendEmptyMessage(MSG_APPLY_DISPLAY_WHITE_BALANCE);
return true;
}
return false;
}
/**
* Reset the CCT value for the display white balance transform to its default value.
*/
public boolean resetDisplayWhiteBalanceColorTemperature() {
return setDisplayWhiteBalanceColorTemperature(getContext().getResources()
.getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault));
}
/**
* Sets the listener and returns whether display white balance is currently enabled.
*/
public boolean setDisplayWhiteBalanceListener(DisplayWhiteBalanceListener listener) {
mDisplayWhiteBalanceListener = listener;
return mDisplayWhiteBalanceTintController.isActivated();
}
/**
* Returns whether Display white balance is currently enabled.
*/
public boolean isDisplayWhiteBalanceEnabled() {
return isDisplayWhiteBalanceSettingEnabled();
}
/**
* Adds a {@link WeakReference<ColorTransformController>} for a newly started activity, and
* invokes {@link ColorTransformController#applyAppSaturation(float[], float[])} if needed.
*/
public boolean attachColorTransformController(String packageName, @UserIdInt int userId,
WeakReference<ColorTransformController> controller) {
return mAppSaturationController
.addColorTransformController(packageName, userId, controller);
}
}
/**
* Listener for changes in display white balance status.
*/
public interface DisplayWhiteBalanceListener {
/**
* Notify that the display white balance status has changed, either due to preemption by
* another transform or the feature being turned off.
*/
void onDisplayWhiteBalanceStatusChanged(boolean activated);
}
private final class TintHandler extends Handler {
private TintHandler(Looper looper) {
super(looper, null, true /* async */);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_USER_CHANGED:
onUserChanged(msg.arg1);
break;
case MSG_SET_UP:
setUp();
break;
case MSG_APPLY_GLOBAL_SATURATION:
mGlobalSaturationTintController.setMatrix(msg.arg1);
applyTint(mGlobalSaturationTintController, false);
break;
case MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE:
applyTint(mNightDisplayTintController, true);
break;
case MSG_APPLY_NIGHT_DISPLAY_ANIMATED:
applyTint(mNightDisplayTintController, false);
break;
case MSG_APPLY_DISPLAY_WHITE_BALANCE:
applyTint(mDisplayWhiteBalanceTintController, false);
break;
}
}
}
/**
* Interface for applying transforms to a given AppWindow.
*/
public interface ColorTransformController {
/**
* Apply the given saturation (grayscale) matrix to the associated AppWindow.
*/
void applyAppSaturation(@Size(9) float[] matrix, @Size(3) float[] translation);
}
@VisibleForTesting
final class BinderService extends IColorDisplayManager.Stub {
@Override
public void setColorMode(int colorMode) {
getContext().enforceCallingOrSelfPermission(
Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
"Permission required to set display color mode");
final long token = Binder.clearCallingIdentity();
try {
setColorModeInternal(colorMode);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public int getColorMode() {
final long token = Binder.clearCallingIdentity();
try {
return getColorModeInternal();
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public boolean isDeviceColorManaged() {
final long token = Binder.clearCallingIdentity();
try {
return isDeviceColorManagedInternal();
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public boolean setSaturationLevel(int level) {
final boolean hasTransformsPermission = getContext()
.checkCallingPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
== PackageManager.PERMISSION_GRANTED;
final boolean hasLegacyPermission = getContext()
.checkCallingPermission(Manifest.permission.CONTROL_DISPLAY_SATURATION)
== PackageManager.PERMISSION_GRANTED;
if (!hasTransformsPermission && !hasLegacyPermission) {
throw new SecurityException("Permission required to set display saturation level");
}
final long token = Binder.clearCallingIdentity();
try {
final Message message = mHandler.obtainMessage(MSG_APPLY_GLOBAL_SATURATION);
message.arg1 = level;
mHandler.sendMessage(message);
} finally {
Binder.restoreCallingIdentity(token);
}
return true;
}
@Override
public boolean isSaturationActivated() {
getContext().enforceCallingPermission(
Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
"Permission required to get display saturation level");
final long token = Binder.clearCallingIdentity();
try {
return !mGlobalSaturationTintController.isActivatedStateNotSet()
&& mGlobalSaturationTintController.isActivated();
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public boolean setAppSaturationLevel(String packageName, int level) {
getContext().enforceCallingPermission(
Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
"Permission required to set display saturation level");
final long token = Binder.clearCallingIdentity();
try {
return setAppSaturationLevelInternal(packageName, level);
} finally {
Binder.restoreCallingIdentity(token);
}
}
public int getTransformCapabilities() {
getContext().enforceCallingPermission(
Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
"Permission required to query transform capabilities");
final long token = Binder.clearCallingIdentity();
try {
return getTransformCapabilitiesInternal();
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public boolean setNightDisplayActivated(boolean activated) {
getContext().enforceCallingOrSelfPermission(
Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
"Permission required to set night display activated");
final long token = Binder.clearCallingIdentity();
try {
mNightDisplayTintController.setActivated(activated);
return true;
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public boolean isNightDisplayActivated() {
final long token = Binder.clearCallingIdentity();
try {
return mNightDisplayTintController.isActivated();
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public boolean setNightDisplayColorTemperature(int temperature) {
getContext().enforceCallingOrSelfPermission(
Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
"Permission required to set night display temperature");
final long token = Binder.clearCallingIdentity();
try {
return mNightDisplayTintController.setColorTemperature(temperature);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public int getNightDisplayColorTemperature() {
final long token = Binder.clearCallingIdentity();
try {
return mNightDisplayTintController.getColorTemperature();
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public boolean setNightDisplayAutoMode(int autoMode) {
getContext().enforceCallingOrSelfPermission(
Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
"Permission required to set night display auto mode");
final long token = Binder.clearCallingIdentity();
try {
return setNightDisplayAutoModeInternal(autoMode);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public int getNightDisplayAutoMode() {
getContext().enforceCallingOrSelfPermission(
Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
"Permission required to get night display auto mode");
final long token = Binder.clearCallingIdentity();
try {
return getNightDisplayAutoModeInternal();
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public int getNightDisplayAutoModeRaw() {
final long token = Binder.clearCallingIdentity();
try {
return getNightDisplayAutoModeRawInternal();
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public boolean setNightDisplayCustomStartTime(Time startTime) {
getContext().enforceCallingOrSelfPermission(
Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
"Permission required to set night display custom start time");
final long token = Binder.clearCallingIdentity();
try {
return setNightDisplayCustomStartTimeInternal(startTime);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public Time getNightDisplayCustomStartTime() {
final long token = Binder.clearCallingIdentity();
try {
return getNightDisplayCustomStartTimeInternal();
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public boolean setNightDisplayCustomEndTime(Time endTime) {
getContext().enforceCallingOrSelfPermission(
Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
"Permission required to set night display custom end time");
final long token = Binder.clearCallingIdentity();
try {
return setNightDisplayCustomEndTimeInternal(endTime);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public Time getNightDisplayCustomEndTime() {
final long token = Binder.clearCallingIdentity();
try {
return getNightDisplayCustomEndTimeInternal();
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public boolean setDisplayWhiteBalanceEnabled(boolean enabled) {
getContext().enforceCallingOrSelfPermission(
Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
"Permission required to set night display activated");
final long token = Binder.clearCallingIdentity();
try {
return setDisplayWhiteBalanceSettingEnabled(enabled);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public boolean isDisplayWhiteBalanceEnabled() {
final long token = Binder.clearCallingIdentity();
try {
return isDisplayWhiteBalanceSettingEnabled();
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
return;
}
final long token = Binder.clearCallingIdentity();
try {
dumpInternal(pw);
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
}