/* * Copyright 2017 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 static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.app.ActivityTaskManager.RootTaskInfo; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ParceledListSlice; import android.database.ContentObserver; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.display.AmbientBrightnessDayStats; import android.hardware.display.BrightnessChangeEvent; import android.hardware.display.BrightnessConfiguration; import android.hardware.display.ColorDisplayManager; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayedContentSample; import android.hardware.display.DisplayedContentSamplingAttributes; import android.hardware.input.InputSensorInfo; import android.os.BatteryManager; import android.os.Handler; import android.os.HandlerThread; import android.os.MessageQueue; import android.os.Parcel; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserManager; import android.provider.Settings; import android.util.AtomicFile; import android.view.Display; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @SmallTest @RunWith(AndroidJUnit4.class) public class BrightnessTrackerTest { private static final float DEFAULT_INITIAL_BRIGHTNESS = 2.5f; private static final boolean DEFAULT_COLOR_SAMPLING_ENABLED = true; private static final String DEFAULT_DISPLAY_ID = "123"; private static final float FLOAT_DELTA = 0.01f; @Mock private InputSensorInfo mInputSensorInfoMock; private BrightnessTracker mTracker; private TestInjector mInjector; private Sensor mLightSensorFake; private static Object sHandlerLock = new Object(); private static Handler sHandler; private static HandlerThread sThread = new HandlerThread("brightness.test", android.os.Process.THREAD_PRIORITY_BACKGROUND); private int mDefaultNightModeColorTemperature; private float mRbcOffsetFactor; private static Handler ensureHandler() { synchronized (sHandlerLock) { if (sHandler == null) { sThread.start(); sHandler = new Handler(sThread.getLooper()); } return sHandler; } } @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mInjector = new TestInjector(ensureHandler()); mLightSensorFake = new Sensor(mInputSensorInfoMock); mTracker = new BrightnessTracker(InstrumentationRegistry.getContext(), mInjector); mTracker.setLightSensor(mLightSensorFake); mDefaultNightModeColorTemperature = InstrumentationRegistry.getContext().getResources().getInteger( R.integer.config_nightDisplayColorTemperatureDefault); mRbcOffsetFactor = InstrumentationRegistry.getContext() .getSystemService(ColorDisplayManager.class).getReduceBrightColorsOffsetFactor(); } @Test public void testStartStopTrackerScreenOnOff() { mInjector.mInteractive = false; startTracker(mTracker); assertNull(mInjector.mSensorListener); assertNotNull(mInjector.mBroadcastReceiver); assertTrue(mInjector.mIdleScheduled); mInjector.sendScreenChange(/* screenOn= */ true); assertNotNull(mInjector.mSensorListener); assertTrue(mInjector.mColorSamplingEnabled); mInjector.sendScreenChange(/* screenOn= */ false); assertNull(mInjector.mSensorListener); assertFalse(mInjector.mColorSamplingEnabled); // Turn screen on while brightness mode is manual mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ false); mInjector.sendScreenChange(/* screenOn= */ true); assertNull(mInjector.mSensorListener); assertFalse(mInjector.mColorSamplingEnabled); // Set brightness mode to automatic while screen is off. mInjector.sendScreenChange(/* screenOn= */ false); mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true); assertNull(mInjector.mSensorListener); assertFalse(mInjector.mColorSamplingEnabled); // Turn on screen while brightness mode is automatic. mInjector.sendScreenChange(/* screenOn= */ true); assertNotNull(mInjector.mSensorListener); assertTrue(mInjector.mColorSamplingEnabled); mTracker.stop(); assertNull(mInjector.mSensorListener); assertNull(mInjector.mBroadcastReceiver); assertFalse(mInjector.mIdleScheduled); assertFalse(mInjector.mColorSamplingEnabled); } @Test public void testModifyBrightnessConfiguration() { mInjector.mInteractive = true; // Start with tracker not listening for color samples. startTracker(mTracker, DEFAULT_INITIAL_BRIGHTNESS, /* collectColorSamples= */ false); assertFalse(mInjector.mColorSamplingEnabled); // Update brightness config to enabled color sampling. mTracker.setShouldCollectColorSample(/* collectColorSamples= */ true); mInjector.waitForHandler(); assertTrue(mInjector.mColorSamplingEnabled); // Update brightness config to disable color sampling. mTracker.setShouldCollectColorSample(/* collectColorSamples= */ false); mInjector.waitForHandler(); assertFalse(mInjector.mColorSamplingEnabled); // Pretend screen is off, update config to turn on color sampling. mInjector.sendScreenChange(/* screenOn= */ false); mTracker.setShouldCollectColorSample(/* collectColorSamples= */ true); mInjector.waitForHandler(); assertFalse(mInjector.mColorSamplingEnabled); // Pretend screen is on. mInjector.sendScreenChange(/* screenOn= */ true); assertTrue(mInjector.mColorSamplingEnabled); mTracker.stop(); assertFalse(mInjector.mColorSamplingEnabled); } @Test public void testNoColorSampling_WrongPixelFormat() { mInjector.mDefaultSamplingAttributes = new DisplayedContentSamplingAttributes( 0x23, mInjector.mDefaultSamplingAttributes.getDataspace(), mInjector.mDefaultSamplingAttributes.getComponentMask()); startTracker(mTracker); assertFalse(mInjector.mColorSamplingEnabled); assertNull(mInjector.mDisplayListener); } @Test public void testNoColorSampling_MissingComponent() { mInjector.mDefaultSamplingAttributes = new DisplayedContentSamplingAttributes( mInjector.mDefaultSamplingAttributes.getPixelFormat(), mInjector.mDefaultSamplingAttributes.getDataspace(), 0x2); startTracker(mTracker); assertFalse(mInjector.mColorSamplingEnabled); assertNull(mInjector.mDisplayListener); } @Test public void testNoColorSampling_NoSupport() { mInjector.mDefaultSamplingAttributes = null; startTracker(mTracker); assertFalse(mInjector.mColorSamplingEnabled); assertNull(mInjector.mDisplayListener); } @Test public void testColorSampling_FrameRateChange() { startTracker(mTracker); assertTrue(mInjector.mColorSamplingEnabled); assertNotNull(mInjector.mDisplayListener); int noFramesSampled = mInjector.mNoColorSamplingFrames; mInjector.mFrameRate = 120.0f; // Wrong display mInjector.mDisplayListener.onDisplayChanged(Display.DEFAULT_DISPLAY + 10); assertEquals(noFramesSampled, mInjector.mNoColorSamplingFrames); // Correct display mInjector.mDisplayListener.onDisplayChanged(Display.DEFAULT_DISPLAY); assertEquals(noFramesSampled * 2, mInjector.mNoColorSamplingFrames); } @Test public void testAdaptiveOnOff() { mInjector.mInteractive = true; mInjector.mIsBrightnessModeAutomatic = false; startTracker(mTracker); assertNull(mInjector.mSensorListener); assertNotNull(mInjector.mBroadcastReceiver); assertNotNull(mInjector.mContentObserver); assertTrue(mInjector.mIdleScheduled); assertFalse(mInjector.mColorSamplingEnabled); assertNull(mInjector.mDisplayListener); mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true); assertNotNull(mInjector.mSensorListener); assertTrue(mInjector.mColorSamplingEnabled); assertNotNull(mInjector.mDisplayListener); SensorEventListener listener = mInjector.mSensorListener; DisplayManager.DisplayListener displayListener = mInjector.mDisplayListener; mInjector.mSensorListener = null; mInjector.mColorSamplingEnabled = false; mInjector.mDisplayListener = null; // Duplicate notification mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true); // Sensor shouldn't have been registered as it was already registered. assertNull(mInjector.mSensorListener); assertFalse(mInjector.mColorSamplingEnabled); assertNull(mInjector.mDisplayListener); mInjector.mDisplayListener = displayListener; mInjector.mColorSamplingEnabled = true; mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ false); assertNull(mInjector.mSensorListener); assertFalse(mInjector.mColorSamplingEnabled); assertNull(mInjector.mDisplayListener); mTracker.stop(); assertNull(mInjector.mSensorListener); assertNull(mInjector.mBroadcastReceiver); assertNull(mInjector.mContentObserver); assertFalse(mInjector.mIdleScheduled); assertFalse(mInjector.mColorSamplingEnabled); assertNull(mInjector.mDisplayListener); } @Test public void testBrightnessEvent() { final float brightness = 0.5f; final String displayId = "1234"; startTracker(mTracker); final long sensorTime = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos()); mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2)); final long currentTime = mInjector.currentTimeMillis(); notifyBrightnessChanged(mTracker, brightness, displayId, new float[] {1.0f}, new long[] {sensorTime}); List events = mTracker.getEvents(0, true).getList(); mTracker.stop(); assertEquals(1, events.size()); BrightnessChangeEvent event = events.get(0); assertEquals(currentTime, event.timeStamp); assertEquals(displayId, event.uniqueDisplayId); assertEquals(1, event.luxValues.length); assertEquals(1.0f, event.luxValues[0], FLOAT_DELTA); assertEquals(currentTime - TimeUnit.SECONDS.toMillis(2), event.luxTimestamps[0]); assertEquals(brightness, event.brightness, FLOAT_DELTA); assertEquals(DEFAULT_INITIAL_BRIGHTNESS, event.lastBrightness, FLOAT_DELTA); // System had no data so these should all be at defaults. assertEquals(Float.NaN, event.batteryLevel, 0.0); assertFalse(event.nightMode); assertEquals(mDefaultNightModeColorTemperature, event.colorTemperature); } @Test public void testMultipleBrightnessEvents() { final float brightnessOne = 0.2f; final float brightnessTwo = 0.4f; final float brightnessThree = 0.6f; final float brightnessFour = 0.3f; final String displayId = "1234"; final float[] luxValues = new float[]{1.0f}; startTracker(mTracker); final long sensorTime = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos()); final long sensorTime2 = sensorTime + TimeUnit.SECONDS.toMillis(20); final long sensorTime3 = sensorTime2 + TimeUnit.SECONDS.toMillis(30); final long sensorTime4 = sensorTime3 + TimeUnit.SECONDS.toMillis(40); final long originalTime = mInjector.currentTimeMillis(); mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2)); notifyBrightnessChanged(mTracker, brightnessOne, displayId, luxValues, new long[] {sensorTime}); mInjector.incrementTime(TimeUnit.SECONDS.toMillis(20)); notifyBrightnessChanged(mTracker, brightnessTwo, displayId, luxValues, new long[] {sensorTime2}); mInjector.incrementTime(TimeUnit.SECONDS.toMillis(30)); notifyBrightnessChanged(mTracker, brightnessThree, displayId, luxValues, new long[] {sensorTime3}); mInjector.incrementTime(TimeUnit.SECONDS.toMillis(40)); notifyBrightnessChanged(mTracker, brightnessFour, displayId, luxValues, new long[] {sensorTime4}); mTracker.stop(); List events = mTracker.getEvents(0, true).getList(); assertEquals(4, events.size()); BrightnessChangeEvent eventOne = events.get(0); assertEquals(brightnessOne, eventOne.brightness, FLOAT_DELTA); assertEquals(originalTime, eventOne.luxTimestamps[0]); BrightnessChangeEvent eventTwo = events.get(1); assertEquals(brightnessTwo, eventTwo.brightness, FLOAT_DELTA); assertEquals(originalTime + TimeUnit.SECONDS.toMillis(20), eventTwo.luxTimestamps[0]); BrightnessChangeEvent eventThree = events.get(2); assertEquals(brightnessThree, eventThree.brightness, FLOAT_DELTA); assertEquals(originalTime + TimeUnit.SECONDS.toMillis(50), eventThree.luxTimestamps[0]); BrightnessChangeEvent eventFour = events.get(3); assertEquals(brightnessFour, eventFour.brightness, FLOAT_DELTA); assertEquals(originalTime + TimeUnit.SECONDS.toMillis(90), eventFour.luxTimestamps[0]); } @Test public void testBrightnessFullPopulatedEvent() { final int initialBrightness = 230; final int brightness = 130; final String displayId = "1234"; mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1); mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3333); mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1); mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL, 40); startTracker(mTracker, initialBrightness, DEFAULT_COLOR_SAMPLING_ENABLED); mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(), batteryChangeEvent(30, 60)); final long currentTime = mInjector.currentTimeMillis(); notifyBrightnessChanged(mTracker, brightness, displayId, new float[] {1000.0f}, new long[] {TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos())}); List eventsNoPackage = mTracker.getEvents(0, false).getList(); List events = mTracker.getEvents(0, true).getList(); mTracker.stop(); assertEquals(1, events.size()); BrightnessChangeEvent event = events.get(0); assertEquals(event.timeStamp, currentTime); assertEquals(displayId, event.uniqueDisplayId); assertArrayEquals(new float[] {1000.0f}, event.luxValues, FLOAT_DELTA); assertArrayEquals(new long[] {currentTime}, event.luxTimestamps); assertEquals(brightness, event.brightness, FLOAT_DELTA); assertEquals(initialBrightness, event.lastBrightness, FLOAT_DELTA); assertEquals(0.5, event.batteryLevel, FLOAT_DELTA); assertTrue(event.nightMode); assertEquals(3333, event.colorTemperature); assertTrue(event.reduceBrightColors); assertEquals(40, event.reduceBrightColorsStrength); assertEquals(brightness * mRbcOffsetFactor, event.reduceBrightColorsOffset, FLOAT_DELTA); assertEquals("a.package", event.packageName); assertEquals(0, event.userId); assertArrayEquals(new long[] {1, 10, 100, 1000, 300, 30, 10, 1}, event.colorValueBuckets); assertEquals(10000, event.colorSampleDuration); assertEquals(1, eventsNoPackage.size()); assertNull(eventsNoPackage.get(0).packageName); } @Test public void testIgnoreAutomaticBrightnessChange() { final int initialBrightness = 30; startTracker(mTracker, initialBrightness, DEFAULT_COLOR_SAMPLING_ENABLED); mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1)); final int systemUpdatedBrightness = 20; notifyBrightnessChanged(mTracker, systemUpdatedBrightness, /* userInitiated= */ false, /* powerBrightnessFactor= */ 0.5f, /* isUserSetBrightness= */ false, /* isDefaultBrightnessConfig= */ false, DEFAULT_DISPLAY_ID); List events = mTracker.getEvents(0, true).getList(); // No events because we filtered out our change. assertEquals(0, events.size()); final int firstUserUpdateBrightness = 20; // Then change comes from somewhere else so we shouldn't filter. notifyBrightnessChanged(mTracker, firstUserUpdateBrightness); // and with a different brightness value. final int secondUserUpdateBrightness = 34; notifyBrightnessChanged(mTracker, secondUserUpdateBrightness); events = mTracker.getEvents(0, true).getList(); assertEquals(2, events.size()); // First event is change from system update (20) to first user update (20) assertEquals(systemUpdatedBrightness, events.get(0).lastBrightness, FLOAT_DELTA); assertEquals(firstUserUpdateBrightness, events.get(0).brightness, FLOAT_DELTA); // Second event is from first to second user update. assertEquals(firstUserUpdateBrightness, events.get(1).lastBrightness, FLOAT_DELTA); assertEquals(secondUserUpdateBrightness, events.get(1).brightness, FLOAT_DELTA); mTracker.stop(); } @Test public void testLimitedBufferSize() { startTracker(mTracker); for (int brightness = 0; brightness <= 255; ++brightness) { mInjector.incrementTime(TimeUnit.SECONDS.toNanos(1)); notifyBrightnessChanged(mTracker, brightness); } List events = mTracker.getEvents(0, true).getList(); mTracker.stop(); // Should be capped at 100 events, and they should be the most recent 100. assertEquals(100, events.size()); for (int i = 0; i < events.size(); i++) { BrightnessChangeEvent event = events.get(i); assertEquals(156 + i, event.brightness, FLOAT_DELTA); } } @Test public void testReadEvents() throws Exception { BrightnessTracker tracker = new BrightnessTracker(InstrumentationRegistry.getContext(), mInjector); mInjector.mCurrentTimeMillis = System.currentTimeMillis(); long someTimeAgo = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(12); long twoMonthsAgo = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(60); // 3 Events in the file but one too old to read. String eventFile = "\n" + "\n" + "" + "" // Event that is too old so shouldn't show up. + "" + ""; tracker.readEventsLocked(getInputStream(eventFile)); List events = tracker.getEvents(0, true).getList(); assertEquals(1, events.size()); BrightnessChangeEvent event = events.get(0); assertEquals(someTimeAgo, event.timeStamp); assertEquals(194.2, event.brightness, FLOAT_DELTA); assertEquals("123", event.uniqueDisplayId); assertArrayEquals(new float[] {32.2f, 31.1f}, event.luxValues, FLOAT_DELTA); assertArrayEquals(new long[] {someTimeAgo, someTimeAgo}, event.luxTimestamps); assertEquals(32.333, event.lastBrightness, FLOAT_DELTA); assertEquals(0, event.userId); assertFalse(event.nightMode); assertFalse(event.reduceBrightColors); assertEquals(1.0f, event.batteryLevel, FLOAT_DELTA); assertEquals("com.example.app", event.packageName); assertTrue(event.isDefaultBrightnessConfig); assertEquals(0.5f, event.powerBrightnessFactor, FLOAT_DELTA); assertTrue(event.isUserSetBrightness); assertNull(event.colorValueBuckets); events = tracker.getEvents(1, true).getList(); assertEquals(1, events.size()); event = events.get(0); assertEquals(someTimeAgo, event.timeStamp); assertEquals(71, event.brightness, FLOAT_DELTA); assertEquals("456", event.uniqueDisplayId); assertArrayEquals(new float[] {132.2f, 131.1f}, event.luxValues, FLOAT_DELTA); assertArrayEquals(new long[] {someTimeAgo, someTimeAgo}, event.luxTimestamps); assertEquals(32, event.lastBrightness, FLOAT_DELTA); assertEquals(1, event.userId); assertTrue(event.nightMode); assertEquals(3235, event.colorTemperature); assertTrue(event.reduceBrightColors); assertEquals(0.5f, event.batteryLevel, FLOAT_DELTA); assertEquals("com.android.anapp", event.packageName); // Not present in the event so default to false. assertFalse(event.isDefaultBrightnessConfig); assertEquals(1.0, event.powerBrightnessFactor, FLOAT_DELTA); assertFalse(event.isUserSetBrightness); assertEquals(3456L, event.colorSampleDuration); assertArrayEquals(new long[] {123L, 598L, 23L, 19L}, event.colorValueBuckets); // Pretend user 1 is a profile of user 0. mInjector.mProfiles = new int[]{0, 1}; events = tracker.getEvents(0, true).getList(); // Both events should now be returned. assertEquals(2, events.size()); BrightnessChangeEvent userZeroEvent; BrightnessChangeEvent userOneEvent; if (events.get(0).userId == 0) { userZeroEvent = events.get(0); userOneEvent = events.get(1); } else { userZeroEvent = events.get(1); userOneEvent = events.get(0); } assertEquals(0, userZeroEvent.userId); assertEquals("com.example.app", userZeroEvent.packageName); assertEquals(1, userOneEvent.userId); // Events from user 1 should have the package name redacted assertNull(userOneEvent.packageName); } @Test public void testFailedRead() { String someTimeAgo = Long.toString(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(12)); mInjector.mCurrentTimeMillis = System.currentTimeMillis(); BrightnessTracker tracker = new BrightnessTracker(InstrumentationRegistry.getContext(), mInjector); String eventFile = "junk in the file"; try { tracker.readEventsLocked(getInputStream(eventFile)); } catch (IOException e) { // Expected; } assertEquals(0, tracker.getEvents(0, true).getList().size()); // Missing lux value. eventFile = "\n" + "\n" + "\n" + ""; try { tracker.readEventsLocked(getInputStream(eventFile)); } catch (IOException e) { // Expected; } assertEquals(0, tracker.getEvents(0, true).getList().size()); } @Test public void testWriteThenRead() throws Exception { final int brightness = 20; final String displayId = "1234"; mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1); mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339); mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1); mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL, 40); startTracker(mTracker); mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(), batteryChangeEvent(30, 100)); final long elapsedTime1 = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos()); final long currentTime1 = mInjector.currentTimeMillis(); mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2)); final long elapsedTime2 = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos()); final long currentTime2 = mInjector.currentTimeMillis(); mInjector.incrementTime(TimeUnit.SECONDS.toMillis(3)); notifyBrightnessChanged(mTracker, brightness, /* userInitiated= */ true, /* powerBrightnessFactor= */ 0.5f, /* isUserSetBrightness= */ true, /* isDefaultBrightnessConfig= */ false, displayId, new float[] {2000.0f, 3000.0f}, new long[] {elapsedTime1, elapsedTime2}); ByteArrayOutputStream baos = new ByteArrayOutputStream(); mTracker.writeEventsLocked(baos); mTracker.stop(); baos.flush(); ByteArrayInputStream input = new ByteArrayInputStream(baos.toByteArray()); BrightnessTracker tracker = new BrightnessTracker(InstrumentationRegistry.getContext(), mInjector); tracker.readEventsLocked(input); List events = tracker.getEvents(0, true).getList(); assertEquals(1, events.size()); BrightnessChangeEvent event = events.get(0); assertEquals(displayId, event.uniqueDisplayId); assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, FLOAT_DELTA); assertArrayEquals(new long[] {currentTime1, currentTime2}, event.luxTimestamps); assertEquals(brightness, event.brightness, FLOAT_DELTA); assertEquals(0.3, event.batteryLevel, FLOAT_DELTA); assertTrue(event.nightMode); assertEquals(3339, event.colorTemperature); assertTrue(event.reduceBrightColors); assertEquals(40, event.reduceBrightColorsStrength); assertEquals(brightness * mRbcOffsetFactor, event.reduceBrightColorsOffset, FLOAT_DELTA); assertEquals(0.5f, event.powerBrightnessFactor, FLOAT_DELTA); assertTrue(event.isUserSetBrightness); assertFalse(event.isDefaultBrightnessConfig); assertArrayEquals(new long[] {1, 10, 100, 1000, 300, 30, 10, 1}, event.colorValueBuckets); assertEquals(10000, event.colorSampleDuration); } @Test public void testParcelUnParcel() { Parcel parcel = Parcel.obtain(); BrightnessChangeEvent.Builder builder = new BrightnessChangeEvent.Builder(); builder.setBrightness(23f); builder.setTimeStamp(345L); builder.setPackageName("com.example"); builder.setUserId(12); builder.setUniqueDisplayId("9876"); float[] luxValues = new float[2]; luxValues[0] = 3000.0f; luxValues[1] = 4000.0f; builder.setLuxValues(luxValues); long[] luxTimestamps = new long[2]; luxTimestamps[0] = 325L; luxTimestamps[1] = 315L; builder.setLuxTimestamps(luxTimestamps); builder.setBatteryLevel(0.7f); builder.setNightMode(false); builder.setColorTemperature(345); builder.setReduceBrightColors(false); builder.setReduceBrightColorsStrength(40); builder.setReduceBrightColorsOffset(20f); builder.setLastBrightness(50f); builder.setColorValues(new long[] {23, 34, 45}, 1000L); BrightnessChangeEvent event = builder.build(); event.writeToParcel(parcel, 0); byte[] parceled = parcel.marshall(); parcel.recycle(); parcel = Parcel.obtain(); parcel.unmarshall(parceled, 0, parceled.length); parcel.setDataPosition(0); BrightnessChangeEvent event2 = BrightnessChangeEvent.CREATOR.createFromParcel(parcel); parcel.recycle(); assertEquals(event.brightness, event2.brightness, FLOAT_DELTA); assertEquals(event.timeStamp, event2.timeStamp); assertEquals(event.packageName, event2.packageName); assertEquals(event.userId, event2.userId); assertEquals(event.uniqueDisplayId, event2.uniqueDisplayId); assertArrayEquals(event.luxValues, event2.luxValues, FLOAT_DELTA); assertArrayEquals(event.luxTimestamps, event2.luxTimestamps); assertEquals(event.batteryLevel, event2.batteryLevel, FLOAT_DELTA); assertEquals(event.nightMode, event2.nightMode); assertEquals(event.colorTemperature, event2.colorTemperature); assertEquals(event.reduceBrightColors, event2.reduceBrightColors); assertEquals(event.reduceBrightColorsStrength, event2.reduceBrightColorsStrength); assertEquals(event.reduceBrightColorsOffset, event2.reduceBrightColorsOffset, FLOAT_DELTA); assertEquals(event.lastBrightness, event2.lastBrightness, FLOAT_DELTA); assertArrayEquals(event.colorValueBuckets, event2.colorValueBuckets); assertEquals(event.colorSampleDuration, event2.colorSampleDuration); parcel = Parcel.obtain(); builder.setBatteryLevel(Float.NaN); event = builder.build(); event.writeToParcel(parcel, 0); parceled = parcel.marshall(); parcel.recycle(); parcel = Parcel.obtain(); parcel.unmarshall(parceled, 0, parceled.length); parcel.setDataPosition(0); event2 = BrightnessChangeEvent.CREATOR.createFromParcel(parcel); assertEquals(event.batteryLevel, event2.batteryLevel, FLOAT_DELTA); } @Test public void testNonNullAmbientStats() { // getAmbientBrightnessStats should return an empty list rather than null when // tracker isn't started or hasn't collected any data. ParceledListSlice slice = mTracker.getAmbientBrightnessStats(0); assertNotNull(slice); assertTrue(slice.getList().isEmpty()); startTracker(mTracker); slice = mTracker.getAmbientBrightnessStats(0); assertNotNull(slice); assertTrue(slice.getList().isEmpty()); } @Test public void testBackgroundHandlerDelay() { final int brightness = 20; // Setup tracker. startTracker(mTracker); mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f)); mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2)); // Block handler from running. final CountDownLatch latch = new CountDownLatch(1); mInjector.mHandler.post( () -> { try { latch.await(); } catch (InterruptedException e) { fail(e.getMessage()); } }); // Send an event. long eventTime = mInjector.currentTimeMillis(); mTracker.notifyBrightnessChanged(brightness, /* userInitiated= */ true, /* powerBrightnessFactor= */ 1.0f, /* isUserSetBrightness= */ false, /* isDefaultBrightnessConfig= */ false, DEFAULT_DISPLAY_ID, new float[10], new long[10]); // Time passes before handler can run. mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2)); // Let the handler run. latch.countDown(); mInjector.waitForHandler(); List events = mTracker.getEvents(0, true).getList(); mTracker.stop(); // Check event was recorded with time it was sent rather than handler ran. assertEquals(1, events.size()); BrightnessChangeEvent event = events.get(0); assertEquals(eventTime, event.timeStamp); } @Test public void testDisplayIdChange() { float firstBrightness = 0.5f; float secondBrightness = 0.75f; String firstDisplayId = "123"; String secondDisplayId = "456"; startTracker(mTracker); mInjector.mSensorListener.onSensorChanged(createSensorEvent(1000.0f)); notifyBrightnessChanged(mTracker, firstBrightness, firstDisplayId); mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2)); List events = mTracker.getEvents(0, true).getList(); assertEquals(1, events.size()); BrightnessChangeEvent firstEvent = events.get(0); assertEquals(firstDisplayId, firstEvent.uniqueDisplayId); assertEquals(firstBrightness, firstEvent.brightness, 0.001f); notifyBrightnessChanged(mTracker, secondBrightness, secondDisplayId); mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2)); events = mTracker.getEvents(0, true).getList(); assertEquals(2, events.size()); BrightnessChangeEvent secondEvent = events.get(1); assertEquals(secondDisplayId, secondEvent.uniqueDisplayId); assertEquals(secondBrightness, secondEvent.brightness, 0.001f); mTracker.stop(); } @Test public void testLightSensorChange() { // verify the tracker started correctly and a listener registered startTracker(mTracker); assertNotNull(mInjector.mSensorListener); assertEquals(mInjector.mLightSensor, mLightSensorFake); // Setting the sensor to null should stop the registered listener. mTracker.setLightSensor(null); mInjector.waitForHandler(); assertNull(mInjector.mSensorListener); assertNull(mInjector.mLightSensor); // Resetting sensor should start listener again mTracker.setLightSensor(mLightSensorFake); mInjector.waitForHandler(); assertNotNull(mInjector.mSensorListener); assertEquals(mInjector.mLightSensor, mLightSensorFake); Sensor secondSensor = new Sensor(mInputSensorInfoMock); // Setting a different listener should keep things working mTracker.setLightSensor(secondSensor); mInjector.waitForHandler(); assertNotNull(mInjector.mSensorListener); assertEquals(mInjector.mLightSensor, secondSensor); } @Test public void testSetLightSensorDoesntStartListener() { mTracker.setLightSensor(mLightSensorFake); assertNull(mInjector.mSensorListener); } @Test public void testNullLightSensorWontRegister() { mTracker.setLightSensor(null); startTracker(mTracker); assertNull(mInjector.mSensorListener); assertNull(mInjector.mLightSensor); } @Test public void testOnlyOneReceiverRegistered() { assertNull(mInjector.mLightSensor); assertNull(mInjector.mSensorListener); assertNull(mInjector.mContentObserver); assertNull(mInjector.mBroadcastReceiver); assertFalse(mInjector.mIdleScheduled); startTracker(mTracker, 0.3f, false); assertNotNull(mInjector.mLightSensor); assertNotNull(mInjector.mSensorListener); assertNotNull(mInjector.mContentObserver); assertNotNull(mInjector.mBroadcastReceiver); assertTrue(mInjector.mIdleScheduled); Sensor registeredLightSensor = mInjector.mLightSensor; SensorEventListener registeredSensorListener = mInjector.mSensorListener; ContentObserver registeredContentObserver = mInjector.mContentObserver; BroadcastReceiver registeredBroadcastReceiver = mInjector.mBroadcastReceiver; mTracker.start(0.3f); assertSame(registeredLightSensor, mInjector.mLightSensor); assertSame(registeredSensorListener, mInjector.mSensorListener); assertSame(registeredContentObserver, mInjector.mContentObserver); assertSame(registeredBroadcastReceiver, mInjector.mBroadcastReceiver); mTracker.stop(); assertNull(mInjector.mLightSensor); assertNull(mInjector.mSensorListener); assertNull(mInjector.mContentObserver); assertNull(mInjector.mBroadcastReceiver); assertFalse(mInjector.mIdleScheduled); // mInjector asserts that we aren't removing a null receiver mTracker.stop(); } private InputStream getInputStream(String data) { return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)); } private Intent batteryChangeEvent(int level, int scale) { Intent intent = new Intent(); intent.setAction(Intent.ACTION_BATTERY_CHANGED); intent.putExtra(BatteryManager.EXTRA_LEVEL, level); intent.putExtra(BatteryManager.EXTRA_SCALE, scale); return intent; } private SensorEvent createSensorEvent(float lux) { SensorEvent event; try { Constructor constr = SensorEvent.class.getDeclaredConstructor(Integer.TYPE); constr.setAccessible(true); event = constr.newInstance(1); } catch (Exception e) { throw new RuntimeException(e); } event.values[0] = lux; event.timestamp = mInjector.mElapsedRealtimeNanos; return event; } private void startTracker(BrightnessTracker tracker) { startTracker(tracker, DEFAULT_INITIAL_BRIGHTNESS, DEFAULT_COLOR_SAMPLING_ENABLED); } private void startTracker(BrightnessTracker tracker, float initialBrightness, boolean collectColorSamples) { tracker.start(initialBrightness); tracker.setShouldCollectColorSample(collectColorSamples); mInjector.waitForHandler(); } private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness) { notifyBrightnessChanged(tracker, brightness, DEFAULT_DISPLAY_ID); } private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness, String displayId) { notifyBrightnessChanged(tracker, brightness, /* userInitiated= */ true, /* powerBrightnessFactor= */ 1.0f, /* isUserSetBrightness= */ false, /* isDefaultBrightnessConfig= */ false, displayId, new float[10], new long[10]); } private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness, String displayId, float[] luxValues, long[] luxTimestamps) { notifyBrightnessChanged(tracker, brightness, /* userInitiated= */ true, /* powerBrightnessFactor= */ 1.0f, /* isUserSetBrightness= */ false, /* isDefaultBrightnessConfig= */ false, displayId, luxValues, luxTimestamps); } private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness, boolean userInitiated, float powerBrightnessFactor, boolean isUserSetBrightness, boolean isDefaultBrightnessConfig, String displayId) { tracker.notifyBrightnessChanged(brightness, userInitiated, powerBrightnessFactor, isUserSetBrightness, isDefaultBrightnessConfig, displayId, new float[10], new long[10]); mInjector.waitForHandler(); } private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness, boolean userInitiated, float powerBrightnessFactor, boolean isUserSetBrightness, boolean isDefaultBrightnessConfig, String displayId, float[] luxValues, long[] luxTimestamps) { tracker.notifyBrightnessChanged(brightness, userInitiated, powerBrightnessFactor, isUserSetBrightness, isDefaultBrightnessConfig, displayId, luxValues, luxTimestamps); mInjector.waitForHandler(); } private BrightnessConfiguration buildBrightnessConfiguration(boolean collectColorSamples) { BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder( /* lux= */ new float[] {0f, 10f, 100f}, /* nits= */ new float[] {1f, 90f, 100f}); builder.setShouldCollectColorSamples(collectColorSamples); return builder.build(); } private static final class Idle implements MessageQueue.IdleHandler { private boolean mIdle; @Override public boolean queueIdle() { synchronized (this) { mIdle = true; notifyAll(); } return false; } public synchronized void waitForIdle() { while (!mIdle) { try { wait(); } catch (InterruptedException e) { } } } } private class TestInjector extends BrightnessTracker.Injector { SensorEventListener mSensorListener; Sensor mLightSensor; BroadcastReceiver mBroadcastReceiver; DisplayManager.DisplayListener mDisplayListener; Map mSecureIntSettings = new HashMap<>(); long mCurrentTimeMillis = System.currentTimeMillis(); long mElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos(); Handler mHandler; boolean mIdleScheduled; boolean mInteractive = true; int[] mProfiles; ContentObserver mContentObserver; boolean mIsBrightnessModeAutomatic = true; boolean mColorSamplingEnabled = false; DisplayedContentSamplingAttributes mDefaultSamplingAttributes = new DisplayedContentSamplingAttributes(0x37, 0, 0x4); float mFrameRate = 60.0f; int mNoColorSamplingFrames; public TestInjector(Handler handler) { mHandler = handler; } void incrementTime(long timeMillis) { mCurrentTimeMillis += timeMillis; mElapsedRealtimeNanos += TimeUnit.MILLISECONDS.toNanos(timeMillis); } void setBrightnessMode(boolean isBrightnessModeAutomatic) { mIsBrightnessModeAutomatic = isBrightnessModeAutomatic; mContentObserver.dispatchChange(false, null); waitForHandler(); } void sendScreenChange(boolean screenOn) { mInteractive = screenOn; Intent intent = new Intent(); intent.setAction(screenOn ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF); mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(), intent); waitForHandler(); } void waitForHandler() { Idle idle = new Idle(); mHandler.getLooper().getQueue().addIdleHandler(idle); mHandler.post(() -> {}); idle.waitForIdle(); } @Override public void registerSensorListener(Context context, SensorEventListener sensorListener, Sensor lightSensor, Handler handler) { mSensorListener = sensorListener; mLightSensor = lightSensor; } @Override public void unregisterSensorListener(Context context, SensorEventListener sensorListener) { mSensorListener = null; mLightSensor = null; } @Override public void registerBrightnessModeObserver(ContentResolver resolver, ContentObserver settingsObserver) { mContentObserver = settingsObserver; } @Override public void unregisterBrightnessModeObserver(Context context, ContentObserver settingsObserver) { mContentObserver = null; } @Override public void registerReceiver(Context context, BroadcastReceiver shutdownReceiver, IntentFilter shutdownFilter) { mBroadcastReceiver = shutdownReceiver; } @Override public void unregisterReceiver(Context context, BroadcastReceiver broadcastReceiver) { assertEquals(mBroadcastReceiver, broadcastReceiver); mBroadcastReceiver = null; } @Override public Handler getBackgroundHandler() { return mHandler; } @Override public boolean isBrightnessModeAutomatic(ContentResolver resolver) { return mIsBrightnessModeAutomatic; } @Override public int getSecureIntForUser(ContentResolver resolver, String setting, int defaultValue, int userId) { Integer value = mSecureIntSettings.get(setting); if (value == null) { return defaultValue; } else { return value; } } @Override public AtomicFile getFile(String filename) { // Don't have the test write / read from anywhere. return null; } @Override public AtomicFile getLegacyFile(String filename) { // Don't have the test write / read from anywhere. return null; } @Override public long currentTimeMillis() { return mCurrentTimeMillis; } @Override public long elapsedRealtimeNanos() { return mElapsedRealtimeNanos; } @Override public int getUserSerialNumber(UserManager userManager, int userId) { return userId + 10; } @Override public int getUserId(UserManager userManager, int userSerialNumber) { return userSerialNumber - 10; } @Override public int[] getProfileIds(UserManager userManager, int userId) { if (mProfiles != null) { return mProfiles; } else { return new int[]{userId}; } } @Override public RootTaskInfo getFocusedStack() throws RemoteException { RootTaskInfo focusedStack = new RootTaskInfo(); focusedStack.userId = 0; focusedStack.topActivity = new ComponentName("a.package", "a.class"); return focusedStack; } @Override public void scheduleIdleJob(Context context) { // Don't actually schedule jobs during unit tests. mIdleScheduled = true; } @Override public void cancelIdleJob(Context context) { mIdleScheduled = false; } @Override public boolean isInteractive(Context context) { return mInteractive; } @Override public int getNightDisplayColorTemperature(Context context) { return mSecureIntSettings.getOrDefault(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, mDefaultNightModeColorTemperature); } @Override public boolean isNightDisplayActivated(Context context) { return mSecureIntSettings.getOrDefault(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 0) == 1; } @Override public int getReduceBrightColorsStrength(Context context) { return mSecureIntSettings.getOrDefault(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL, 0); } @Override public boolean isReduceBrightColorsActivated(Context context) { return mSecureIntSettings.getOrDefault(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 0) == 1; } @Override public DisplayedContentSample sampleColor(int noFramesToSample) { return new DisplayedContentSample(600L, null, null, new long[] {1, 10, 100, 1000, 300, 30, 10, 1}, null); } @Override public float getFrameRate(Context context) { return mFrameRate; } @Override public DisplayedContentSamplingAttributes getSamplingAttributes() { return mDefaultSamplingAttributes; } @Override public boolean enableColorSampling(boolean enable, int noFrames) { mColorSamplingEnabled = enable; mNoColorSamplingFrames = noFrames; return true; } @Override public void registerDisplayListener(Context context, DisplayManager.DisplayListener listener, Handler handler) { mDisplayListener = listener; } @Override public void unRegisterDisplayListener(Context context, DisplayManager.DisplayListener listener) { mDisplayListener = null; } } }