1 /*
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 
17 package com.android.server.display.color;
18 
19 import android.annotation.UserIdInt;
20 import android.util.ArrayMap;
21 import android.util.SparseArray;
22 
23 import com.android.internal.annotations.GuardedBy;
24 import com.android.internal.annotations.VisibleForTesting;
25 import com.android.server.display.color.ColorDisplayService.ColorTransformController;
26 
27 import java.io.PrintWriter;
28 import java.lang.ref.WeakReference;
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.Iterator;
33 import java.util.List;
34 import java.util.Map;
35 
36 class AppSaturationController {
37 
38     private final Object mLock = new Object();
39 
40     /**
41      * A package name has one or more userIds it is running under. Each userId has zero or one
42      * saturation level, and zero or more ColorTransformControllers.
43      */
44     @GuardedBy("mLock")
45     private final Map<String, SparseArray<SaturationController>> mAppsMap = new HashMap<>();
46 
47     @VisibleForTesting
48     static final float[] TRANSLATION_VECTOR = {0f, 0f, 0f};
49 
50     /**
51      * Add an {@link WeakReference<ColorTransformController>} for a given package and userId.
52      */
addColorTransformController(String packageName, @UserIdInt int userId, WeakReference<ColorTransformController> controller)53     boolean addColorTransformController(String packageName, @UserIdInt int userId,
54             WeakReference<ColorTransformController> controller) {
55         synchronized (mLock) {
56             return getSaturationControllerLocked(packageName, userId)
57                     .addColorTransformController(controller);
58         }
59     }
60 
61     /**
62      * Set the saturation level ({@code ColorDisplayManager#SaturationLevel} constant for a given
63      * package name and userId.
64      */
setSaturationLevel(String callingPackageName, String affectedPackageName, @UserIdInt int userId, int saturationLevel)65     public boolean setSaturationLevel(String callingPackageName, String affectedPackageName,
66             @UserIdInt int userId,
67             int saturationLevel) {
68         synchronized (mLock) {
69             return getSaturationControllerLocked(affectedPackageName, userId)
70                     .setSaturationLevel(callingPackageName, saturationLevel);
71         }
72     }
73 
74     /**
75      * Dump state information.
76      */
dump(PrintWriter pw)77     public void dump(PrintWriter pw) {
78         synchronized (mLock) {
79             pw.println("App Saturation: ");
80             if (mAppsMap.size() == 0) {
81                 pw.println("    No packages");
82                 return;
83             }
84             final List<String> packageNames = new ArrayList<>(mAppsMap.keySet());
85             Collections.sort(packageNames);
86             for (String packageName : packageNames) {
87                 pw.println("    " + packageName + ":");
88                 final SparseArray<SaturationController> appUserIdMap = mAppsMap.get(packageName);
89                 for (int i = 0; i < appUserIdMap.size(); i++) {
90                     pw.println("        " + appUserIdMap.keyAt(i) + ":");
91                     appUserIdMap.valueAt(i).dump(pw);
92                 }
93             }
94         }
95     }
96 
97     /**
98      * Retrieve the SaturationController for a given package and userId, creating all intermediate
99      * connections as needed.
100      */
getSaturationControllerLocked(String packageName, @UserIdInt int userId)101     private SaturationController getSaturationControllerLocked(String packageName,
102             @UserIdInt int userId) {
103         return getOrCreateSaturationControllerLocked(getOrCreateUserIdMapLocked(packageName),
104                 userId);
105     }
106 
107     /**
108      * Retrieve or create the mapping between the app's given package name and its userIds (and
109      * their SaturationControllers).
110      */
getOrCreateUserIdMapLocked(String packageName)111     private SparseArray<SaturationController> getOrCreateUserIdMapLocked(String packageName) {
112         if (mAppsMap.get(packageName) != null) {
113             return mAppsMap.get(packageName);
114         }
115 
116         final SparseArray<SaturationController> appUserIdMap = new SparseArray<>();
117         mAppsMap.put(packageName, appUserIdMap);
118         return appUserIdMap;
119     }
120 
121     /**
122      * Retrieve or create the mapping between an app's given userId and SaturationController.
123      */
getOrCreateSaturationControllerLocked( SparseArray<SaturationController> appUserIdMap, @UserIdInt int userId)124     private SaturationController getOrCreateSaturationControllerLocked(
125             SparseArray<SaturationController> appUserIdMap, @UserIdInt int userId) {
126         if (appUserIdMap.get(userId) != null) {
127             return appUserIdMap.get(userId);
128         }
129 
130         final SaturationController saturationController = new SaturationController();
131         appUserIdMap.put(userId, saturationController);
132         return saturationController;
133     }
134 
135     @VisibleForTesting
computeGrayscaleTransformMatrix(float saturation, float[] matrix)136     static void computeGrayscaleTransformMatrix(float saturation, float[] matrix) {
137         float desaturation = 1.0f - saturation;
138         float[] luminance = {0.231f * desaturation, 0.715f * desaturation,
139                 0.072f * desaturation};
140         matrix[0] = luminance[0] + saturation;
141         matrix[1] = luminance[0];
142         matrix[2] = luminance[0];
143         matrix[3] = luminance[1];
144         matrix[4] = luminance[1] + saturation;
145         matrix[5] = luminance[1];
146         matrix[6] = luminance[2];
147         matrix[7] = luminance[2];
148         matrix[8] = luminance[2] + saturation;
149     }
150 
151     private static class SaturationController {
152 
153         private static final int FULL_SATURATION = 100;
154 
155         private final List<WeakReference<ColorTransformController>> mControllerRefs =
156                 new ArrayList<>();
157         private final ArrayMap<String, Integer> mSaturationLevels = new ArrayMap<>();
158         private float[] mTransformMatrix = new float[9];
159 
setSaturationLevel(String callingPackageName, int saturationLevel)160         private boolean setSaturationLevel(String callingPackageName, int saturationLevel) {
161             if (saturationLevel == FULL_SATURATION) {
162                 mSaturationLevels.remove(callingPackageName);
163             } else {
164                 mSaturationLevels.put(callingPackageName, saturationLevel);
165             }
166             if (!mControllerRefs.isEmpty()) {
167                 return updateState();
168             }
169             return false;
170         }
171 
addColorTransformController( WeakReference<ColorTransformController> controller)172         private boolean addColorTransformController(
173                 WeakReference<ColorTransformController> controller) {
174             clearExpiredReferences();
175             mControllerRefs.add(controller);
176             if (!mSaturationLevels.isEmpty()) {
177                 return updateState();
178             }
179             return false;
180         }
181 
calculateSaturationLevel()182         private int calculateSaturationLevel() {
183             int saturationLevel = FULL_SATURATION;
184             for (int i = 0; i < mSaturationLevels.size(); i++) {
185                 final int level = mSaturationLevels.valueAt(i);
186                 if (level < saturationLevel) {
187                     saturationLevel = level;
188                 }
189             }
190             return saturationLevel;
191         }
192 
updateState()193         private boolean updateState() {
194             computeGrayscaleTransformMatrix(calculateSaturationLevel() / 100f, mTransformMatrix);
195 
196             boolean updated = false;
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                     controller.applyAppSaturation(mTransformMatrix, TRANSLATION_VECTOR);
204                     updated = true;
205                 } else {
206                     // Purge cleared refs lazily to avoid accumulating a lot of dead windows
207                     iterator.remove();
208                 }
209             }
210             return updated;
211         }
212 
clearExpiredReferences()213         private void clearExpiredReferences() {
214             final Iterator<WeakReference<ColorTransformController>> iterator = mControllerRefs
215                     .iterator();
216             while (iterator.hasNext()) {
217                 WeakReference<ColorTransformController> controllerRef = iterator.next();
218                 final ColorTransformController controller = controllerRef.get();
219                 if (controller == null) {
220                     iterator.remove();
221                 }
222             }
223         }
224 
dump(PrintWriter pw)225         private void dump(PrintWriter pw) {
226             pw.println("            mSaturationLevels: " + mSaturationLevels);
227             pw.println("            mControllerRefs count: " + mControllerRefs.size());
228         }
229     }
230 }
231