| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.display.color; |
| |
| import android.annotation.UserIdInt; |
| import android.util.ArrayMap; |
| import android.util.SparseArray; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.server.display.color.ColorDisplayService.ColorTransformController; |
| |
| import java.io.PrintWriter; |
| import java.lang.ref.WeakReference; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| class AppSaturationController { |
| |
| private final Object mLock = new Object(); |
| |
| /** |
| * A package name has one or more userIds it is running under. Each userId has zero or one |
| * saturation level, and zero or more ColorTransformControllers. |
| */ |
| @GuardedBy("mLock") |
| private final Map<String, SparseArray<SaturationController>> mAppsMap = new HashMap<>(); |
| |
| @VisibleForTesting |
| static final float[] TRANSLATION_VECTOR = {0f, 0f, 0f}; |
| |
| /** |
| * Add an {@link WeakReference<ColorTransformController>} for a given package and userId. |
| */ |
| boolean addColorTransformController(String packageName, @UserIdInt int userId, |
| WeakReference<ColorTransformController> controller) { |
| synchronized (mLock) { |
| return getSaturationControllerLocked(packageName, userId) |
| .addColorTransformController(controller); |
| } |
| } |
| |
| /** |
| * Set the saturation level ({@code ColorDisplayManager#SaturationLevel} constant for a given |
| * package name and userId. |
| */ |
| public boolean setSaturationLevel(String callingPackageName, String affectedPackageName, |
| @UserIdInt int userId, |
| int saturationLevel) { |
| synchronized (mLock) { |
| return getSaturationControllerLocked(affectedPackageName, userId) |
| .setSaturationLevel(callingPackageName, saturationLevel); |
| } |
| } |
| |
| /** |
| * Dump state information. |
| */ |
| public void dump(PrintWriter pw) { |
| synchronized (mLock) { |
| pw.println("App Saturation: "); |
| if (mAppsMap.size() == 0) { |
| pw.println(" No packages"); |
| return; |
| } |
| final List<String> packageNames = new ArrayList<>(mAppsMap.keySet()); |
| Collections.sort(packageNames); |
| for (String packageName : packageNames) { |
| pw.println(" " + packageName + ":"); |
| final SparseArray<SaturationController> appUserIdMap = mAppsMap.get(packageName); |
| for (int i = 0; i < appUserIdMap.size(); i++) { |
| pw.println(" " + appUserIdMap.keyAt(i) + ":"); |
| appUserIdMap.valueAt(i).dump(pw); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Retrieve the SaturationController for a given package and userId, creating all intermediate |
| * connections as needed. |
| */ |
| private SaturationController getSaturationControllerLocked(String packageName, |
| @UserIdInt int userId) { |
| return getOrCreateSaturationControllerLocked(getOrCreateUserIdMapLocked(packageName), |
| userId); |
| } |
| |
| /** |
| * Retrieve or create the mapping between the app's given package name and its userIds (and |
| * their SaturationControllers). |
| */ |
| private SparseArray<SaturationController> getOrCreateUserIdMapLocked(String packageName) { |
| if (mAppsMap.get(packageName) != null) { |
| return mAppsMap.get(packageName); |
| } |
| |
| final SparseArray<SaturationController> appUserIdMap = new SparseArray<>(); |
| mAppsMap.put(packageName, appUserIdMap); |
| return appUserIdMap; |
| } |
| |
| /** |
| * Retrieve or create the mapping between an app's given userId and SaturationController. |
| */ |
| private SaturationController getOrCreateSaturationControllerLocked( |
| SparseArray<SaturationController> appUserIdMap, @UserIdInt int userId) { |
| if (appUserIdMap.get(userId) != null) { |
| return appUserIdMap.get(userId); |
| } |
| |
| final SaturationController saturationController = new SaturationController(); |
| appUserIdMap.put(userId, saturationController); |
| return saturationController; |
| } |
| |
| @VisibleForTesting |
| static void computeGrayscaleTransformMatrix(float saturation, float[] matrix) { |
| float desaturation = 1.0f - saturation; |
| float[] luminance = {0.231f * desaturation, 0.715f * desaturation, |
| 0.072f * desaturation}; |
| matrix[0] = luminance[0] + saturation; |
| matrix[1] = luminance[0]; |
| matrix[2] = luminance[0]; |
| matrix[3] = luminance[1]; |
| matrix[4] = luminance[1] + saturation; |
| matrix[5] = luminance[1]; |
| matrix[6] = luminance[2]; |
| matrix[7] = luminance[2]; |
| matrix[8] = luminance[2] + saturation; |
| } |
| |
| private static class SaturationController { |
| |
| private static final int FULL_SATURATION = 100; |
| |
| private final List<WeakReference<ColorTransformController>> mControllerRefs = |
| new ArrayList<>(); |
| private final ArrayMap<String, Integer> mSaturationLevels = new ArrayMap<>(); |
| private float[] mTransformMatrix = new float[9]; |
| |
| private boolean setSaturationLevel(String callingPackageName, int saturationLevel) { |
| if (saturationLevel == FULL_SATURATION) { |
| mSaturationLevels.remove(callingPackageName); |
| } else { |
| mSaturationLevels.put(callingPackageName, saturationLevel); |
| } |
| if (!mControllerRefs.isEmpty()) { |
| return updateState(); |
| } |
| return false; |
| } |
| |
| private boolean addColorTransformController( |
| WeakReference<ColorTransformController> controller) { |
| clearExpiredReferences(); |
| mControllerRefs.add(controller); |
| if (!mSaturationLevels.isEmpty()) { |
| return updateState(); |
| } |
| return false; |
| } |
| |
| private int calculateSaturationLevel() { |
| int saturationLevel = FULL_SATURATION; |
| for (int i = 0; i < mSaturationLevels.size(); i++) { |
| final int level = mSaturationLevels.valueAt(i); |
| if (level < saturationLevel) { |
| saturationLevel = level; |
| } |
| } |
| return saturationLevel; |
| } |
| |
| private boolean updateState() { |
| computeGrayscaleTransformMatrix(calculateSaturationLevel() / 100f, mTransformMatrix); |
| |
| boolean updated = false; |
| final Iterator<WeakReference<ColorTransformController>> iterator = mControllerRefs |
| .iterator(); |
| while (iterator.hasNext()) { |
| WeakReference<ColorTransformController> controllerRef = iterator.next(); |
| final ColorTransformController controller = controllerRef.get(); |
| if (controller != null) { |
| controller.applyAppSaturation(mTransformMatrix, TRANSLATION_VECTOR); |
| updated = true; |
| } else { |
| // Purge cleared refs lazily to avoid accumulating a lot of dead windows |
| iterator.remove(); |
| } |
| } |
| return updated; |
| } |
| |
| private void clearExpiredReferences() { |
| final Iterator<WeakReference<ColorTransformController>> iterator = mControllerRefs |
| .iterator(); |
| while (iterator.hasNext()) { |
| WeakReference<ColorTransformController> controllerRef = iterator.next(); |
| final ColorTransformController controller = controllerRef.get(); |
| if (controller == null) { |
| iterator.remove(); |
| } |
| } |
| } |
| |
| private void dump(PrintWriter pw) { |
| pw.println(" mSaturationLevels: " + mSaturationLevels); |
| pw.println(" mControllerRefs count: " + mControllerRefs.size()); |
| } |
| } |
| } |