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