Christine Franks | f3529b2 | 2019-01-03 13:20:17 -0800 | [diff] [blame] | 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 | |
Christine Franks | 0ada277 | 2019-02-25 13:54:57 -0800 | [diff] [blame] | 17 | package com.android.server.display.color; |
Christine Franks | f3529b2 | 2019-01-03 13:20:17 -0800 | [diff] [blame] | 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; |
Christine Franks | 0ada277 | 2019-02-25 13:54:57 -0800 | [diff] [blame] | 24 | import com.android.server.display.color.ColorDisplayService.ColorTransformController; |
Christine Franks | f3529b2 | 2019-01-03 13:20:17 -0800 | [diff] [blame] | 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 | */ |
| 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 | } |