Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5daa3568 authored by Christine Franks's avatar Christine Franks Committed by Android (Google) Code Review
Browse files

Merge "Add per-app grayscale to CDS"

parents 01c1c07c f3529b2f
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -1770,6 +1770,7 @@ package android.hardware.display {
  }
  public final class ColorDisplayManager {
    method public boolean setAppSaturationLevel(java.lang.String, int);
    method public boolean setSaturationLevel(int);
  }
+26 −2
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
package android.hardware.display;

import android.Manifest;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -61,15 +63,29 @@ public final class ColorDisplayManager {
     *
     * @param saturationLevel 0-100 (inclusive), where 100 is full saturation
     * @return whether the saturation level change was applied successfully
     *
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
    public boolean setSaturationLevel(int saturationLevel) {
    public boolean setSaturationLevel(@IntRange(from = 0, to = 100) int saturationLevel) {
        return mManager.setSaturationLevel(saturationLevel);
    }

    /**
     * Set the level of color saturation to apply to a specific app.
     *
     * @param packageName the package name of the app whose windows should be desaturated
     * @param saturationLevel 0-100 (inclusive), where 100 is full saturation
     * @return whether the saturation level change was applied successfully
     * @hide
     */
    @SystemApi
    @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
    public boolean setAppSaturationLevel(@NonNull String packageName,
            @IntRange(from = 0, to = 100) int saturationLevel) {
        return mManager.setAppSaturationLevel(packageName, saturationLevel);
    }

    /**
     * Returns {@code true} if Night Display is supported by the device.
     *
@@ -128,5 +144,13 @@ public final class ColorDisplayManager {
                throw e.rethrowFromSystemServer();
            }
        }

        boolean setAppSaturationLevel(String packageName, int saturationLevel) {
            try {
                return mCdm.setAppSaturationLevel(packageName, saturationLevel);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -21,4 +21,5 @@ interface IColorDisplayManager {
    boolean isDeviceColorManaged();

    boolean setSaturationLevel(int saturationLevel);
    boolean setAppSaturationLevel(String packageName, int saturationLevel);
}
 No newline at end of file
+213 −0
Original line number Diff line number Diff line
/*
 * 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;

import android.annotation.UserIdInt;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.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 packageName, @UserIdInt int userId,
            int saturationLevel) {
        synchronized (mLock) {
            return getSaturationControllerLocked(packageName, userId)
                    .setSaturationLevel(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 final List<WeakReference<ColorTransformController>> mControllerRefs =
                new ArrayList<>();
        private int mSaturationLevel = 100;
        private float[] mTransformMatrix = new float[9];

        private boolean setSaturationLevel(int saturationLevel) {
            mSaturationLevel = saturationLevel;
            if (!mControllerRefs.isEmpty()) {
                return updateState();
            }
            return false;
        }

        private boolean addColorTransformController(
                WeakReference<ColorTransformController> controller) {
            mControllerRefs.add(controller);
            if (mSaturationLevel != 100) {
                return updateState();
            } else {
                clearExpiredReferences();
            }
            return false;
        }

        private boolean updateState() {
            computeGrayscaleTransformMatrix(mSaturationLevel / 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("            mSaturationLevel: " + mSaturationLevel);
            pw.println("            mControllerRefs count: " + mControllerRefs.size());
        }
    }
}
+67 −1
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
import android.app.AlarmManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -36,8 +37,8 @@ import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.display.ColorDisplayManager;
import android.graphics.ColorSpace;
import android.hardware.display.ColorDisplayManager;
import android.hardware.display.IColorDisplayManager;
import android.net.Uri;
import android.opengl.Matrix;
@@ -55,13 +56,16 @@ import android.view.animation.AnimationUtils;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ColorDisplayController;
import com.android.internal.util.DumpUtils;
import com.android.server.DisplayThread;
import com.android.server.SystemService;
import com.android.server.twilight.TwilightListener;
import com.android.server.twilight.TwilightManager;
import com.android.server.twilight.TwilightState;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.time.DateTimeException;
import java.time.Instant;
import java.time.LocalDateTime;
@@ -400,6 +404,8 @@ public final class ColorDisplayService extends SystemService {

    private final Handler mHandler;

    private final AppSaturationController mAppSaturationController = new AppSaturationController();

    private int mCurrentUser = UserHandle.USER_NULL;
    private ContentObserver mUserSetupObserver;
    private boolean mBootCompleted;
@@ -829,6 +835,22 @@ public final class ColorDisplayService extends SystemService {
        return LocalDateTime.MIN;
    }

    private boolean setAppSaturationLevelInternal(String packageName, int saturationLevel) {
        return mAppSaturationController
                .setSaturationLevel(packageName, mCurrentUser, saturationLevel);
    }

    private void dumpInternal(PrintWriter pw) {
        pw.println("COLOR DISPLAY MANAGER dumpsys (color_display)");
        pw.println("Night Display:");
        if (ColorDisplayManager.isNightDisplayAvailable(getContext())) {
            pw.println("    Activated: " + mNightDisplayTintController.isActivated());
        } else {
            pw.println("    Not available");
        }
        mAppSaturationController.dump(pw);
    }

    private abstract class NightDisplayAutoMode {

        public abstract void onActivated(boolean activated);
@@ -1132,6 +1154,16 @@ public final class ColorDisplayService extends SystemService {
        public void dump(PrintWriter pw) {
            mDisplayWhiteBalanceTintController.dump(pw);
        }

        /**
         * Adds a {@link WeakReference<ColorTransformController>} for a newly started activity, and
         * invokes {@link ColorTransformController#applyAppSaturation(float[], float[])} if needed.
         */
        public boolean attachColorTransformController(String packageName, int uid,
                WeakReference<ColorTransformController> controller) {
            return mAppSaturationController
                    .addColorTransformController(packageName, uid, controller);
        }
    }

    /**
@@ -1163,6 +1195,15 @@ public final class ColorDisplayService extends SystemService {
        }
    }

    /**
     * Interface for applying transforms to a given AppWindow.
     */
    public interface ColorTransformController {

        /** Apply the given saturation (grayscale) matrix to the associated AppWindow. */
        void applyAppSaturation(@Size(9) float[] matrix, @Size(3) float[] translation);
    }

    private final class BinderService extends IColorDisplayManager.Stub {

        @Override
@@ -1196,5 +1237,30 @@ public final class ColorDisplayService extends SystemService {
            }
            return true;
        }

        @Override
        public boolean setAppSaturationLevel(String packageName, int level) {
            getContext().enforceCallingPermission(
                    Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
                    "Permission required to set display saturation level");
            final long token = Binder.clearCallingIdentity();
            try {
                return setAppSaturationLevelInternal(packageName, level);
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }

        @Override
        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
            if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;

            final long token = Binder.clearCallingIdentity();
            try {
                dumpInternal(pw);
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }
    }
}
Loading