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.SparseArray;
21 
22 import com.android.internal.annotations.GuardedBy;
23 import com.android.internal.annotations.VisibleForTesting;
24 import com.android.server.display.color.ColorDisplayService.ColorTransformController;
25 
26 import java.io.PrintWriter;
27 import java.lang.ref.WeakReference;
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.Iterator;
32 import java.util.List;
33 import java.util.Map;
34 
35 class 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      */
addColorTransformController(String packageName, @UserIdInt int userId, WeakReference<ColorTransformController> controller)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      */
setSaturationLevel(String packageName, @UserIdInt int userId, int saturationLevel)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      */
dump(PrintWriter pw)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      */
getSaturationControllerLocked(String packageName, @UserIdInt int userId)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      */
getOrCreateUserIdMapLocked(String packageName)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      */
getOrCreateSaturationControllerLocked( SparseArray<SaturationController> appUserIdMap, @UserIdInt int userId)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
computeGrayscaleTransformMatrix(float saturation, float[] matrix)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 
setSaturationLevel(int saturationLevel)156         private boolean setSaturationLevel(int saturationLevel) {
157             mSaturationLevel = saturationLevel;
158             if (!mControllerRefs.isEmpty()) {
159                 return updateState();
160             }
161             return false;
162         }
163 
addColorTransformController( WeakReference<ColorTransformController> controller)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 
updateState()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 
clearExpiredReferences()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 
dump(PrintWriter pw)208         private void dump(PrintWriter pw) {
209             pw.println("            mSaturationLevel: " + mSaturationLevel);
210             pw.println("            mControllerRefs count: " + mControllerRefs.size());
211         }
212     }
213 }
214