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