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

Commit 908108aa authored by Kenny Guy's avatar Kenny Guy
Browse files

Pipe through color sampling to BrightnessChangeEvent

Change brightness tracker to enable / disable color
sampling on screen on / off.
Add value channel to BrightnessChangeEvent if available.

Bug: 112756444
Test: atest BrightnessTrackerTest
Test: atest android.display.cts.BrightnessTest
Test: manual checked pixel3 which supports this and pixel2 which doesn't

Change-Id: I94384752235d891cee975b01f933c0bc0b4572a9
parent 530c508e
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -1774,7 +1774,9 @@ package android.hardware.display {
    field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessChangeEvent> CREATOR;
    field public final float batteryLevel;
    field public final float brightness;
    field public final long colorSampleDuration;
    field public final int colorTemperature;
    field @Nullable public final long[] colorValueBuckets;
    field public final boolean isDefaultBrightnessConfig;
    field public final boolean isUserSetBrightness;
    field public final float lastBrightness;
+2 −0
Original line number Diff line number Diff line
@@ -580,7 +580,9 @@ package android.hardware.display {
    field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessChangeEvent> CREATOR;
    field public final float batteryLevel;
    field public final float brightness;
    field public final long colorSampleDuration;
    field public final int colorTemperature;
    field @Nullable public final long[] colorValueBuckets;
    field public final boolean isDefaultBrightnessConfig;
    field public final boolean isUserSetBrightness;
    field public final float lastBrightness;
+42 −2
Original line number Diff line number Diff line
@@ -16,11 +16,15 @@

package android.hardware.display;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;

import java.util.Objects;

/**
 * Data about a brightness settings change.
 *
@@ -72,12 +76,29 @@ public final class BrightnessChangeEvent implements Parcelable {
    /** Whether brightness curve includes a user brightness point */
    public final boolean isUserSetBrightness;

    /**
     * Histogram counting how many times a pixel of a given value was displayed onscreen for the
     * Value component of HSV if the device supports color sampling, if the device does not support
     * color sampling the value will be null.
     * The buckets of the histogram are evenly weighted, the number of buckets is device specific.
     * For example if we had {10, 6, 4, 1} this means that 10 pixels were in the range
     * [0x00,0x3f], 6 pixels were in the range [0x40,0x7f] etc.
     */
    @Nullable
    public final long[] colorValueBuckets;

    /**
     * How many milliseconds of data are contained in the colorValueBuckets.
     */
    public final long colorSampleDuration;


    /** @hide */
    private BrightnessChangeEvent(float brightness, long timeStamp, String packageName,
            int userId, float[] luxValues, long[] luxTimestamps, float batteryLevel,
            float powerBrightnessFactor, boolean nightMode, int colorTemperature,
            float lastBrightness, boolean isDefaultBrightnessConfig, boolean isUserSetBrightness) {
            float lastBrightness, boolean isDefaultBrightnessConfig, boolean isUserSetBrightness,
            long[] colorValueBuckets, long colorSampleDuration) {
        this.brightness = brightness;
        this.timeStamp = timeStamp;
        this.packageName = packageName;
@@ -91,6 +112,8 @@ public final class BrightnessChangeEvent implements Parcelable {
        this.lastBrightness = lastBrightness;
        this.isDefaultBrightnessConfig = isDefaultBrightnessConfig;
        this.isUserSetBrightness = isUserSetBrightness;
        this.colorValueBuckets = colorValueBuckets;
        this.colorSampleDuration = colorSampleDuration;
    }

    /** @hide */
@@ -108,6 +131,8 @@ public final class BrightnessChangeEvent implements Parcelable {
        this.lastBrightness = other.lastBrightness;
        this.isDefaultBrightnessConfig = other.isDefaultBrightnessConfig;
        this.isUserSetBrightness = other.isUserSetBrightness;
        this.colorValueBuckets = other.colorValueBuckets;
        this.colorSampleDuration = other.colorSampleDuration;
    }

    private BrightnessChangeEvent(Parcel source) {
@@ -124,6 +149,8 @@ public final class BrightnessChangeEvent implements Parcelable {
        lastBrightness = source.readFloat();
        isDefaultBrightnessConfig = source.readBoolean();
        isUserSetBrightness = source.readBoolean();
        colorValueBuckets = source.createLongArray();
        colorSampleDuration = source.readLong();
    }

    public static final Creator<BrightnessChangeEvent> CREATOR =
@@ -156,6 +183,8 @@ public final class BrightnessChangeEvent implements Parcelable {
        dest.writeFloat(lastBrightness);
        dest.writeBoolean(isDefaultBrightnessConfig);
        dest.writeBoolean(isUserSetBrightness);
        dest.writeLongArray(colorValueBuckets);
        dest.writeLong(colorSampleDuration);
    }

    /** @hide */
@@ -173,6 +202,8 @@ public final class BrightnessChangeEvent implements Parcelable {
        private float mLastBrightness;
        private boolean mIsDefaultBrightnessConfig;
        private boolean mIsUserSetBrightness;
        private long[] mColorValueBuckets;
        private long mColorSampleDuration;

        /** {@see BrightnessChangeEvent#brightness} */
        public Builder setBrightness(float brightness) {
@@ -252,12 +283,21 @@ public final class BrightnessChangeEvent implements Parcelable {
            return this;
        }

        /** {@see BrightnessChangeEvent#valueBuckets} */
        public Builder setColorValues(@NonNull long[] colorValueBuckets, long colorSampleDuration) {
            Objects.requireNonNull(colorValueBuckets);
            mColorValueBuckets = colorValueBuckets;
            mColorSampleDuration = colorSampleDuration;
            return this;
        }

        /** Builds a BrightnessChangeEvent */
        public BrightnessChangeEvent build() {
            return new BrightnessChangeEvent(mBrightness, mTimeStamp,
                    mPackageName, mUserId, mLuxValues, mLuxTimestamps, mBatteryLevel,
                    mPowerBrightnessFactor, mNightMode, mColorTemperature, mLastBrightness,
                    mIsDefaultBrightnessConfig, mIsUserSetBrightness);
                    mIsDefaultBrightnessConfig, mIsUserSetBrightness, mColorValueBuckets,
                    mColorSampleDuration);
        }
    }
}
+6 −0
Original line number Diff line number Diff line
@@ -90,6 +90,9 @@ public class PixelFormat {
    public static final int RGBA_F16     = 0x16;
    public static final int RGBA_1010102 = 0x2B;

    /** @hide */
    public static final int HSV_888 = 0x37;

    /**
     * @deprecated use {@link android.graphics.ImageFormat#JPEG
     * ImageFormat.JPEG} instead.
@@ -109,6 +112,7 @@ public class PixelFormat {
                info.bytesPerPixel = 4;
                break;
            case RGB_888:
            case HSV_888:
                info.bitsPerPixel = 24;
                info.bytesPerPixel = 3;
                break;
@@ -227,6 +231,8 @@ public class PixelFormat {
                return "RGBA_F16";
            case RGBA_1010102:
                return "RGBA_1010102";
            case HSV_888:
                return "HSV_888";
            case JPEG:
                return "JPEG";
            default:
+187 −2
Original line number Diff line number Diff line
@@ -27,12 +27,17 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ParceledListSlice;
import android.database.ContentObserver;
import android.graphics.PixelFormat;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.display.AmbientBrightnessDayStats;
import android.hardware.display.BrightnessChangeEvent;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayedContentSample;
import android.hardware.display.DisplayedContentSamplingAttributes;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.Environment;
@@ -48,6 +53,7 @@ import android.provider.Settings;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.Xml;
import android.view.Display;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -55,6 +61,7 @@ import com.android.internal.app.ColorDisplayController;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.RingBuffer;
import com.android.server.LocalServices;

import libcore.io.IoUtils;

@@ -111,6 +118,8 @@ public class BrightnessTracker {
    private static final String ATTR_DEFAULT_CONFIG = "defaultConfig";
    private static final String ATTR_POWER_SAVE = "powerSaveFactor";
    private static final String ATTR_USER_POINT = "userPoint";
    private static final String ATTR_COLOR_SAMPLE_DURATION = "colorSampleDuration";
    private static final String ATTR_COLOR_VALUE_BUCKETS = "colorValueBuckets";

    private static final int MSG_BACKGROUND_START = 0;
    private static final int MSG_BRIGHTNESS_CHANGED = 1;
@@ -119,6 +128,10 @@ public class BrightnessTracker {

    private static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");

    private static final long COLOR_SAMPLE_DURATION = TimeUnit.SECONDS.toSeconds(10);
    // Sample chanel 2 of HSV which is the Value component.
    private static final int COLOR_SAMPLE_COMPONENT_MASK = 0x1 << 2;

    // Lock held while accessing mEvents, is held while writing events to flash.
    private final Object mEventsLock = new Object();
    @GuardedBy("mEventsLock")
@@ -136,12 +149,16 @@ public class BrightnessTracker {
    private final ContentResolver mContentResolver;
    private final Handler mBgHandler;

    // mBroadcastReceiver,  mSensorListener, mSettingsObserver and mSensorRegistered
    // should only be used on the mBgHandler thread.
    // These members should only be accessed on the mBgHandler thread.
    private BroadcastReceiver mBroadcastReceiver;
    private SensorListener mSensorListener;
    private SettingsObserver mSettingsObserver;
    private DisplayListener mDisplayListener;
    private boolean mSensorRegistered;
    private boolean mColorSamplingEnabled;
    private int mNoFramesToSample;
    private float mFrameRate;
    // End of block of members that should only be accessed on the mBgHandler thread.

    private @UserIdInt int mCurrentUserId = UserHandle.USER_NULL;

@@ -208,6 +225,7 @@ public class BrightnessTracker {
            mLastBrightness = initialBrightness;
            mStarted = true;
        }
        enableColorSampling();
    }

    /** Stop listening for events */
@@ -226,6 +244,7 @@ public class BrightnessTracker {
        synchronized (mDataCollectionLock) {
            mStarted = false;
        }
        disableColorSampling();
    }

    public void onSwitchUser(@UserIdInt int newUserId) {
@@ -367,6 +386,17 @@ public class BrightnessTracker {
        builder.setColorTemperature(mInjector.getColorTemperature(mContext,
                UserHandle.USER_CURRENT));

        if (mColorSamplingEnabled) {
            DisplayedContentSample sample = mInjector.sampleColor(mNoFramesToSample);
            if (sample != null && sample.getSampleComponent(
                    DisplayedContentSample.ColorComponent.CHANNEL2) != null) {
                float numMillis = (sample.getNumFrames() / mFrameRate) * 1000.0f;
                builder.setColorValues(
                        sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL2),
                        Math.round(numMillis));
            }
        }

        BrightnessChangeEvent event = builder.build();
        if (DEBUG) {
            Slog.d(TAG, "Event " + event.brightness + " " + event.packageName);
@@ -541,6 +571,19 @@ public class BrightnessTracker {
                }
                out.attribute(null, ATTR_LUX, luxValues.toString());
                out.attribute(null, ATTR_LUX_TIMESTAMPS, luxTimestamps.toString());
                if (toWrite[i].colorValueBuckets != null
                        && toWrite[i].colorValueBuckets.length > 0) {
                    out.attribute(null, ATTR_COLOR_SAMPLE_DURATION,
                            Long.toString(toWrite[i].colorSampleDuration));
                    StringBuilder buckets = new StringBuilder();
                    for (int j = 0; j < toWrite[i].colorValueBuckets.length; ++j) {
                        if (j > 0) {
                            buckets.append(',');
                        }
                        buckets.append(Long.toString(toWrite[i].colorValueBuckets[j]));
                    }
                    out.attribute(null, ATTR_COLOR_VALUE_BUCKETS, buckets.toString());
                }
                out.endTag(null, TAG_EVENT);
            }
        }
@@ -628,6 +671,20 @@ public class BrightnessTracker {
                        builder.setUserBrightnessPoint(Boolean.parseBoolean(userPoint));
                    }

                    String colorSampleDurationString =
                            parser.getAttributeValue(null, ATTR_COLOR_SAMPLE_DURATION);
                    String colorValueBucketsString =
                            parser.getAttributeValue(null, ATTR_COLOR_VALUE_BUCKETS);
                    if (colorSampleDurationString != null && colorValueBucketsString != null) {
                        long colorSampleDuration = Long.parseLong(colorSampleDurationString);
                        String[] buckets = colorValueBucketsString.split(",");
                        long[] bucketValues = new long[buckets.length];
                        for (int i = 0; i < bucketValues.length; ++i) {
                            bucketValues[i] = Long.parseLong(buckets[i]);
                        }
                        builder.setColorValues(bucketValues, colorSampleDuration);
                    }

                    BrightnessChangeEvent event = builder.build();
                    if (DEBUG) {
                        Slog.i(TAG, "Read event " + event.brightness
@@ -695,6 +752,73 @@ public class BrightnessTracker {

    private void dumpLocal(PrintWriter pw) {
        pw.println("  mSensorRegistered=" + mSensorRegistered);
        pw.println("  mColorSamplingEnabled=" + mColorSamplingEnabled);
        pw.println("  mNoFramesToSample=" + mNoFramesToSample);
        pw.println("  mFrameRate=" + mFrameRate);
    }

    private void enableColorSampling() {
        if (!mInjector.isBrightnessModeAutomatic(mContentResolver)
                || !mInjector.isInteractive(mContext)
                || mColorSamplingEnabled) {
            return;
        }

        mFrameRate = mInjector.getFrameRate(mContext);
        if (mFrameRate <= 0) {
            Slog.wtf(TAG, "Default display has a zero or negative framerate.");
            return;
        }
        mNoFramesToSample = (int) (mFrameRate * COLOR_SAMPLE_DURATION);

        DisplayedContentSamplingAttributes attributes = mInjector.getSamplingAttributes();
        if (DEBUG && attributes != null) {
            Slog.d(TAG, "Color sampling"
                    + " mask=0x" + Integer.toHexString(attributes.getComponentMask())
                    + " dataSpace=0x" + Integer.toHexString(attributes.getDataspace())
                    + " pixelFormat=0x" + Integer.toHexString(attributes.getPixelFormat()));
        }
        // Do we support sampling the Value component of HSV
        if (attributes != null && attributes.getPixelFormat() == PixelFormat.HSV_888
                && (attributes.getComponentMask() & COLOR_SAMPLE_COMPONENT_MASK) != 0) {

            mColorSamplingEnabled = mInjector.enableColorSampling(/* enable= */true,
                    mNoFramesToSample);
            if (DEBUG) {
                Slog.i(TAG, "turning on color sampling for "
                        + mNoFramesToSample + " frames, success=" + mColorSamplingEnabled);
            }
        }
        if (mColorSamplingEnabled && mDisplayListener == null) {
            mDisplayListener = new DisplayListener();
            mInjector.registerDisplayListener(mContext, mDisplayListener, mBgHandler);
        }
    }

    private void disableColorSampling() {
        if (!mColorSamplingEnabled) {
            return;
        }
        mInjector.enableColorSampling(/* enable= */ false, /* noFrames= */ 0);
        mColorSamplingEnabled = false;
        if (mDisplayListener != null) {
            mInjector.unRegisterDisplayListener(mContext, mDisplayListener);
            mDisplayListener = null;
        }
        if (DEBUG) {
            Slog.i(TAG, "turning off color sampling");
        }
    }

    private void updateColorSampling() {
        if (!mColorSamplingEnabled) {
            return;
        }
        float frameRate = mInjector.getFrameRate(mContext);
        if (frameRate != mFrameRate) {
            disableColorSampling();
            enableColorSampling();
        }
    }

    public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats(int userId) {
@@ -768,6 +892,26 @@ public class BrightnessTracker {
        }
    }

    private final class DisplayListener implements DisplayManager.DisplayListener {

        @Override
        public void onDisplayAdded(int displayId) {
            // Ignore
        }

        @Override
        public void onDisplayRemoved(int displayId) {
            // Ignore
        }

        @Override
        public void onDisplayChanged(int displayId) {
            if (displayId == Display.DEFAULT_DISPLAY) {
                updateColorSampling();
            }
        }
    }

    private final class SettingsObserver extends ContentObserver {
        public SettingsObserver(Handler handler) {
            super(handler);
@@ -828,9 +972,11 @@ public class BrightnessTracker {
                    break;
                case MSG_START_SENSOR_LISTENER:
                    startSensorListener();
                    enableColorSampling();
                    break;
                case MSG_STOP_SENSOR_LISTENER:
                    stopSensorListener();
                    disableColorSampling();
                    break;
            }
        }
@@ -957,5 +1103,44 @@ public class BrightnessTracker {
        public boolean isNightModeActive(Context context, int userId) {
            return new ColorDisplayController(context, userId).isActivated();
        }

        public DisplayedContentSample sampleColor(int noFramesToSample) {
            final DisplayManagerInternal displayManagerInternal =
                    LocalServices.getService(DisplayManagerInternal.class);
            return displayManagerInternal.getDisplayedContentSample(
                   Display.DEFAULT_DISPLAY, noFramesToSample, 0);
        }

        public float getFrameRate(Context context) {
            final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
            Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
            return display.getRefreshRate();
        }

        public DisplayedContentSamplingAttributes getSamplingAttributes() {
            final DisplayManagerInternal displayManagerInternal =
                    LocalServices.getService(DisplayManagerInternal.class);
            return displayManagerInternal.getDisplayedContentSamplingAttributes(
                    Display.DEFAULT_DISPLAY);
        }

        public boolean enableColorSampling(boolean enable, int noFrames) {
            final DisplayManagerInternal displayManagerInternal =
                    LocalServices.getService(DisplayManagerInternal.class);
            return displayManagerInternal.setDisplayedContentSamplingEnabled(
                    Display.DEFAULT_DISPLAY, enable, COLOR_SAMPLE_COMPONENT_MASK, noFrames);
        }

        public void registerDisplayListener(Context context,
                DisplayManager.DisplayListener listener, Handler handler) {
            final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
            displayManager.registerDisplayListener(listener, handler);
        }

        public void unRegisterDisplayListener(Context context,
                DisplayManager.DisplayListener listener) {
            final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
            displayManager.unregisterDisplayListener(listener);
        }
    }
}
Loading