blob: e42be02d4a9e58720e00cc6097a5bb97d6afba8e [file] [log] [blame]
Christine Franksf3529b22019-01-03 13:20:17 -08001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Christine Franks0ada2772019-02-25 13:54:57 -080017package com.android.server.display.color;
Christine Franksf3529b22019-01-03 13:20:17 -080018
19import android.annotation.UserIdInt;
20import android.util.SparseArray;
21
22import com.android.internal.annotations.GuardedBy;
23import com.android.internal.annotations.VisibleForTesting;
Christine Franks0ada2772019-02-25 13:54:57 -080024import com.android.server.display.color.ColorDisplayService.ColorTransformController;
Christine Franksf3529b22019-01-03 13:20:17 -080025
26import java.io.PrintWriter;
27import java.lang.ref.WeakReference;
28import java.util.ArrayList;
29import java.util.Collections;
30import java.util.HashMap;
31import java.util.Iterator;
32import java.util.List;
33import java.util.Map;
34
35class AppSaturationController {
36
37 private final Object mLock = new Object();
38
39 /**
40 * A package name has one or more userIds it is running under. Each userId has zero or one
41 * saturation level, and zero or more ColorTransformControllers.
42 */
43 @GuardedBy("mLock")
44 private final Map<String, SparseArray<SaturationController>> mAppsMap = new HashMap<>();
45
46 @VisibleForTesting
47 static final float[] TRANSLATION_VECTOR = {0f, 0f, 0f};
48
49 /**
50 * Add an {@link WeakReference<ColorTransformController>} for a given package and userId.
51 */
52 boolean addColorTransformController(String packageName, @UserIdInt int userId,
53 WeakReference<ColorTransformController> controller) {
54 synchronized (mLock) {
55 return getSaturationControllerLocked(packageName, userId)
56 .addColorTransformController(controller);
57 }
58 }
59
60 /**
61 * Set the saturation level ({@code ColorDisplayManager#SaturationLevel} constant for a given
62 * package name and userId.
63 */
64 public boolean setSaturationLevel(String packageName, @UserIdInt int userId,
65 int saturationLevel) {
66 synchronized (mLock) {
67 return getSaturationControllerLocked(packageName, userId)
68 .setSaturationLevel(saturationLevel);
69 }
70 }
71
72 /**
73 * Dump state information.
74 */
75 public void dump(PrintWriter pw) {
76 synchronized (mLock) {
77 pw.println("App Saturation: ");
78 if (mAppsMap.size() == 0) {
79 pw.println(" No packages");
80 return;
81 }
82 final List<String> packageNames = new ArrayList<>(mAppsMap.keySet());
83 Collections.sort(packageNames);
84 for (String packageName : packageNames) {
85 pw.println(" " + packageName + ":");
86 final SparseArray<SaturationController> appUserIdMap = mAppsMap.get(packageName);
87 for (int i = 0; i < appUserIdMap.size(); i++) {
88 pw.println(" " + appUserIdMap.keyAt(i) + ":");
89 appUserIdMap.valueAt(i).dump(pw);
90 }
91 }
92 }
93 }
94
95 /**
96 * Retrieve the SaturationController for a given package and userId, creating all intermediate
97 * connections as needed.
98 */
99 private SaturationController getSaturationControllerLocked(String packageName,
100 @UserIdInt int userId) {
101 return getOrCreateSaturationControllerLocked(getOrCreateUserIdMapLocked(packageName),
102 userId);
103 }
104
105 /**
106 * Retrieve or create the mapping between the app's given package name and its userIds (and
107 * their SaturationControllers).
108 */
109 private SparseArray<SaturationController> getOrCreateUserIdMapLocked(String packageName) {
110 if (mAppsMap.get(packageName) != null) {
111 return mAppsMap.get(packageName);
112 }
113
114 final SparseArray<SaturationController> appUserIdMap = new SparseArray<>();
115 mAppsMap.put(packageName, appUserIdMap);
116 return appUserIdMap;
117 }
118
119 /**
120 * Retrieve or create the mapping between an app's given userId and SaturationController.
121 */
122 private SaturationController getOrCreateSaturationControllerLocked(
123 SparseArray<SaturationController> appUserIdMap, @UserIdInt int userId) {
124 if (appUserIdMap.get(userId) != null) {
125 return appUserIdMap.get(userId);
126 }
127
128 final SaturationController saturationController = new SaturationController();
129 appUserIdMap.put(userId, saturationController);
130 return saturationController;
131 }
132
133 @VisibleForTesting
134 static void computeGrayscaleTransformMatrix(float saturation, float[] matrix) {
135 float desaturation = 1.0f - saturation;
136 float[] luminance = {0.231f * desaturation, 0.715f * desaturation,
137 0.072f * desaturation};
138 matrix[0] = luminance[0] + saturation;
139 matrix[1] = luminance[0];
140 matrix[2] = luminance[0];
141 matrix[3] = luminance[1];
142 matrix[4] = luminance[1] + saturation;
143 matrix[5] = luminance[1];
144 matrix[6] = luminance[2];
145 matrix[7] = luminance[2];
146 matrix[8] = luminance[2] + saturation;
147 }
148
149 private static class SaturationController {
150
151 private final List<WeakReference<ColorTransformController>> mControllerRefs =
152 new ArrayList<>();
153 private int mSaturationLevel = 100;
154 private float[] mTransformMatrix = new float[9];
155
156 private boolean setSaturationLevel(int saturationLevel) {
157 mSaturationLevel = saturationLevel;
158 if (!mControllerRefs.isEmpty()) {
159 return updateState();
160 }
161 return false;
162 }
163
164 private boolean addColorTransformController(
165 WeakReference<ColorTransformController> controller) {
166 mControllerRefs.add(controller);
167 if (mSaturationLevel != 100) {
168 return updateState();
169 } else {
170 clearExpiredReferences();
171 }
172 return false;
173 }
174
175 private boolean updateState() {
176 computeGrayscaleTransformMatrix(mSaturationLevel / 100f, mTransformMatrix);
177
178 boolean updated = false;
179 final Iterator<WeakReference<ColorTransformController>> iterator = mControllerRefs
180 .iterator();
181 while (iterator.hasNext()) {
182 WeakReference<ColorTransformController> controllerRef = iterator.next();
183 final ColorTransformController controller = controllerRef.get();
184 if (controller != null) {
185 controller.applyAppSaturation(mTransformMatrix, TRANSLATION_VECTOR);
186 updated = true;
187 } else {
188 // Purge cleared refs lazily to avoid accumulating a lot of dead windows
189 iterator.remove();
190 }
191 }
192 return updated;
193
194 }
195
196 private void clearExpiredReferences() {
197 final Iterator<WeakReference<ColorTransformController>> iterator = mControllerRefs
198 .iterator();
199 while (iterator.hasNext()) {
200 WeakReference<ColorTransformController> controllerRef = iterator.next();
201 final ColorTransformController controller = controllerRef.get();
202 if (controller == null) {
203 iterator.remove();
204 }
205 }
206 }
207
208 private void dump(PrintWriter pw) {
209 pw.println(" mSaturationLevel: " + mSaturationLevel);
210 pw.println(" mControllerRefs count: " + mControllerRefs.size());
211 }
212 }
213}