1/*
2 * Copyright 2017 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
17package com.android.server.display;
18
19import static org.junit.Assert.assertArrayEquals;
20import static org.junit.Assert.assertEquals;
21import static org.junit.Assert.assertFalse;
22import static org.junit.Assert.assertNotNull;
23import static org.junit.Assert.assertNull;
24import static org.junit.Assert.assertTrue;
25import static org.junit.Assert.fail;
26
27import android.app.ActivityManager;
28import android.content.BroadcastReceiver;
29import android.content.ComponentName;
30import android.content.ContentResolver;
31import android.content.Context;
32import android.content.Intent;
33import android.content.IntentFilter;
34import android.content.pm.ParceledListSlice;
35import android.database.ContentObserver;
36import android.hardware.SensorEvent;
37import android.hardware.SensorEventListener;
38import android.hardware.display.AmbientBrightnessDayStats;
39import android.hardware.display.BrightnessChangeEvent;
40import android.os.BatteryManager;
41import android.os.Handler;
42import android.os.HandlerThread;
43import android.os.MessageQueue;
44import android.os.Parcel;
45import android.os.RemoteException;
46import android.os.SystemClock;
47import android.os.UserManager;
48import android.provider.Settings;
49import android.support.test.InstrumentationRegistry;
50import android.support.test.filters.SmallTest;
51import android.support.test.runner.AndroidJUnit4;
52import android.util.AtomicFile;
53
54import org.junit.Before;
55import org.junit.Test;
56import org.junit.runner.RunWith;
57
58import java.io.ByteArrayInputStream;
59import java.io.ByteArrayOutputStream;
60import java.io.IOException;
61import java.io.InputStream;
62import java.lang.reflect.Constructor;
63import java.nio.charset.StandardCharsets;
64import java.util.HashMap;
65import java.util.List;
66import java.util.Map;
67import java.util.concurrent.CountDownLatch;
68import java.util.concurrent.TimeUnit;
69
70@SmallTest
71@RunWith(AndroidJUnit4.class)
72public class BrightnessTrackerTest {
73    private static final float DEFAULT_INITIAL_BRIGHTNESS = 2.5f;
74    private static final float FLOAT_DELTA = 0.01f;
75
76    private BrightnessTracker mTracker;
77    private TestInjector mInjector;
78
79    private static Object sHandlerLock = new Object();
80    private static Handler sHandler;
81    private static HandlerThread sThread =
82            new HandlerThread("brightness.test", android.os.Process.THREAD_PRIORITY_BACKGROUND);
83
84    private static Handler ensureHandler() {
85        synchronized (sHandlerLock) {
86            if (sHandler == null) {
87                sThread.start();
88                sHandler = new Handler(sThread.getLooper());
89            }
90            return sHandler;
91        }
92    }
93
94
95    @Before
96    public void setUp() throws Exception {
97        mInjector = new TestInjector(ensureHandler());
98
99        mTracker = new BrightnessTracker(InstrumentationRegistry.getContext(), mInjector);
100    }
101
102    @Test
103    public void testStartStopTrackerScreenOnOff() {
104        mInjector.mInteractive = false;
105        startTracker(mTracker);
106        assertNull(mInjector.mSensorListener);
107        assertNotNull(mInjector.mBroadcastReceiver);
108        assertTrue(mInjector.mIdleScheduled);
109        mInjector.sendScreenChange(/*screen on */ true);
110        assertNotNull(mInjector.mSensorListener);
111
112        mInjector.sendScreenChange(/*screen on */ false);
113        assertNull(mInjector.mSensorListener);
114
115        // Turn screen on while brightness mode is manual
116        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic */ false);
117        mInjector.sendScreenChange(/*screen on */ true);
118        assertNull(mInjector.mSensorListener);
119
120        // Set brightness mode to automatic while screen is off.
121        mInjector.sendScreenChange(/*screen on */ false);
122        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic */ true);
123        assertNull(mInjector.mSensorListener);
124
125        // Turn on screen while brightness mode is automatic.
126        mInjector.sendScreenChange(/*screen on */ true);
127        assertNotNull(mInjector.mSensorListener);
128
129        mTracker.stop();
130        assertNull(mInjector.mSensorListener);
131        assertNull(mInjector.mBroadcastReceiver);
132        assertFalse(mInjector.mIdleScheduled);
133    }
134
135    @Test
136    public void testAdaptiveOnOff() {
137        mInjector.mInteractive = true;
138        mInjector.mIsBrightnessModeAutomatic = false;
139        startTracker(mTracker);
140        assertNull(mInjector.mSensorListener);
141        assertNotNull(mInjector.mBroadcastReceiver);
142        assertNotNull(mInjector.mContentObserver);
143        assertTrue(mInjector.mIdleScheduled);
144
145        mInjector.setBrightnessMode(/*isBrightnessModeAutomatic*/ true);
146        assertNotNull(mInjector.mSensorListener);
147
148        SensorEventListener listener = mInjector.mSensorListener;
149        mInjector.mSensorListener = null;
150        // Duplicate notification
151        mInjector.setBrightnessMode(/*isBrightnessModeAutomatic*/ true);
152        // Sensor shouldn't have been registered as it was already registered.
153        assertNull(mInjector.mSensorListener);
154        mInjector.mSensorListener = listener;
155
156        mInjector.setBrightnessMode(/*isBrightnessModeAutomatic*/ false);
157        assertNull(mInjector.mSensorListener);
158
159        mTracker.stop();
160        assertNull(mInjector.mSensorListener);
161        assertNull(mInjector.mBroadcastReceiver);
162        assertNull(mInjector.mContentObserver);
163        assertFalse(mInjector.mIdleScheduled);
164    }
165
166    @Test
167    public void testBrightnessEvent() {
168        final int brightness = 20;
169
170        startTracker(mTracker);
171        mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f));
172        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
173        notifyBrightnessChanged(mTracker, brightness);
174        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
175        mTracker.stop();
176
177        assertEquals(1, events.size());
178        BrightnessChangeEvent event = events.get(0);
179        assertEquals(mInjector.currentTimeMillis(), event.timeStamp);
180        assertEquals(1, event.luxValues.length);
181        assertEquals(1.0f, event.luxValues[0], FLOAT_DELTA);
182        assertEquals(mInjector.currentTimeMillis() - TimeUnit.SECONDS.toMillis(2),
183                event.luxTimestamps[0]);
184        assertEquals(brightness, event.brightness, FLOAT_DELTA);
185        assertEquals(DEFAULT_INITIAL_BRIGHTNESS, event.lastBrightness, FLOAT_DELTA);
186
187        // System had no data so these should all be at defaults.
188        assertEquals(Float.NaN, event.batteryLevel, 0.0);
189        assertFalse(event.nightMode);
190        assertEquals(0, event.colorTemperature);
191    }
192
193    @Test
194    public void testBrightnessFullPopulatedEvent() {
195        final int initialBrightness = 230;
196        final int brightness = 130;
197
198        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
199        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3333);
200
201        startTracker(mTracker, initialBrightness);
202        mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
203                batteryChangeEvent(30, 60));
204        mInjector.mSensorListener.onSensorChanged(createSensorEvent(1000.0f));
205        final long sensorTime = mInjector.currentTimeMillis();
206        notifyBrightnessChanged(mTracker, brightness);
207        List<BrightnessChangeEvent> eventsNoPackage
208                = mTracker.getEvents(0, false).getList();
209        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
210        mTracker.stop();
211
212        assertEquals(1, events.size());
213        BrightnessChangeEvent event = events.get(0);
214        assertEquals(event.timeStamp, mInjector.currentTimeMillis());
215        assertArrayEquals(new float[] {1000.0f}, event.luxValues, 0.01f);
216        assertArrayEquals(new long[] {sensorTime}, event.luxTimestamps);
217        assertEquals(brightness, event.brightness, FLOAT_DELTA);
218        assertEquals(initialBrightness, event.lastBrightness, FLOAT_DELTA);
219        assertEquals(0.5, event.batteryLevel, FLOAT_DELTA);
220        assertTrue(event.nightMode);
221        assertEquals(3333, event.colorTemperature);
222        assertEquals("a.package", event.packageName);
223        assertEquals(0, event.userId);
224
225        assertEquals(1, eventsNoPackage.size());
226        assertNull(eventsNoPackage.get(0).packageName);
227    }
228
229    @Test
230    public void testIgnoreAutomaticBrightnessChange() {
231        final int initialBrightness = 30;
232        startTracker(mTracker, initialBrightness);
233        mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f));
234        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1));
235
236        final int systemUpdatedBrightness = 20;
237        notifyBrightnessChanged(mTracker, systemUpdatedBrightness, false /*userInitiated*/,
238                0.5f /*powerBrightnessFactor(*/, false /*isUserSetBrightness*/,
239                false /*isDefaultBrightnessConfig*/);
240        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
241        // No events because we filtered out our change.
242        assertEquals(0, events.size());
243
244        final int firstUserUpdateBrightness = 20;
245        // Then change comes from somewhere else so we shouldn't filter.
246        notifyBrightnessChanged(mTracker, firstUserUpdateBrightness);
247
248        // and with a different brightness value.
249        final int secondUserUpdateBrightness = 34;
250        notifyBrightnessChanged(mTracker, secondUserUpdateBrightness);
251        events = mTracker.getEvents(0, true).getList();
252
253        assertEquals(2, events.size());
254        // First event is change from system update (20) to first user update (20)
255        assertEquals(systemUpdatedBrightness, events.get(0).lastBrightness, FLOAT_DELTA);
256        assertEquals(firstUserUpdateBrightness, events.get(0).brightness, FLOAT_DELTA);
257        // Second event is from first to second user update.
258        assertEquals(firstUserUpdateBrightness, events.get(1).lastBrightness, FLOAT_DELTA);
259        assertEquals(secondUserUpdateBrightness, events.get(1).brightness, FLOAT_DELTA);
260
261        mTracker.stop();
262    }
263
264    @Test
265    public void testLimitedBufferSize() {
266        startTracker(mTracker);
267        mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f));
268
269        for (int brightness = 0; brightness <= 255; ++brightness) {
270            mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f));
271            mInjector.incrementTime(TimeUnit.SECONDS.toNanos(1));
272            notifyBrightnessChanged(mTracker, brightness);
273        }
274        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
275        mTracker.stop();
276
277        // Should be capped at 100 events, and they should be the most recent 100.
278        assertEquals(100, events.size());
279        for (int i = 0; i < events.size(); i++) {
280            BrightnessChangeEvent event = events.get(i);
281            assertEquals(156 + i, event.brightness, FLOAT_DELTA);
282        }
283    }
284
285    @Test
286    public void testLimitedSensorEvents() {
287        final int brightness = 20;
288
289        startTracker(mTracker);
290        // 20 Sensor events 1 second apart.
291        for (int i = 0; i < 20; ++i) {
292            mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1));
293            mInjector.mSensorListener.onSensorChanged(createSensorEvent(i + 1.0f));
294        }
295        notifyBrightnessChanged(mTracker, 20);
296        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
297        mTracker.stop();
298
299        assertEquals(1, events.size());
300        BrightnessChangeEvent event = events.get(0);
301        assertEquals(mInjector.currentTimeMillis(), event.timeStamp);
302
303        // 12 sensor events, 11 for 0->10 seconds + 1 previous event.
304        assertEquals(12, event.luxValues.length);
305        for (int i = 0; i < 12; ++i) {
306            assertEquals(event.luxTimestamps[11 - i],
307                    mInjector.currentTimeMillis() - i * TimeUnit.SECONDS.toMillis(1));
308        }
309        assertEquals(brightness, event.brightness, FLOAT_DELTA);
310    }
311
312    @Test
313    public void testReadEvents() throws Exception {
314        BrightnessTracker tracker = new BrightnessTracker(InstrumentationRegistry.getContext(),
315                mInjector);
316        mInjector.mCurrentTimeMillis = System.currentTimeMillis();
317        long someTimeAgo = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(12);
318        long twoMonthsAgo = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(60);
319        // 3 Events in the file but one too old to read.
320        String eventFile =
321                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
322                + "<events>\n"
323                + "<event nits=\"194.2\" timestamp=\""
324                + Long.toString(someTimeAgo) + "\" packageName=\""
325                + "com.example.app\" user=\"10\" "
326                + "lastNits=\"32.333\" "
327                + "batteryLevel=\"1.0\" nightMode=\"false\" colorTemperature=\"0\"\n"
328                + "lux=\"32.2,31.1\" luxTimestamps=\""
329                + Long.toString(someTimeAgo) + "," + Long.toString(someTimeAgo) + "\""
330                + "defaultConfig=\"true\" powerSaveFactor=\"0.5\" userPoint=\"true\" />"
331                + "<event nits=\"71\" timestamp=\""
332                + Long.toString(someTimeAgo) + "\" packageName=\""
333                + "com.android.anapp\" user=\"11\" "
334                + "lastNits=\"32\" "
335                + "batteryLevel=\"0.5\" nightMode=\"true\" colorTemperature=\"3235\"\n"
336                + "lux=\"132.2,131.1\" luxTimestamps=\""
337                + Long.toString(someTimeAgo) + "," + Long.toString(someTimeAgo) + "\"/>"
338                // Event that is too old so shouldn't show up.
339                + "<event nits=\"142\" timestamp=\""
340                + Long.toString(twoMonthsAgo) + "\" packageName=\""
341                + "com.example.app\" user=\"10\" "
342                + "lastNits=\"32\" "
343                + "batteryLevel=\"1.0\" nightMode=\"false\" colorTemperature=\"0\"\n"
344                + "lux=\"32.2,31.1\" luxTimestamps=\""
345                + Long.toString(twoMonthsAgo) + "," + Long.toString(twoMonthsAgo) + "\"/>"
346                + "</events>";
347        tracker.readEventsLocked(getInputStream(eventFile));
348        List<BrightnessChangeEvent> events = tracker.getEvents(0, true).getList();
349        assertEquals(1, events.size());
350        BrightnessChangeEvent event = events.get(0);
351        assertEquals(someTimeAgo, event.timeStamp);
352        assertEquals(194.2, event.brightness, FLOAT_DELTA);
353        assertArrayEquals(new float[] {32.2f, 31.1f}, event.luxValues, FLOAT_DELTA);
354        assertArrayEquals(new long[] {someTimeAgo, someTimeAgo}, event.luxTimestamps);
355        assertEquals(32.333, event.lastBrightness, FLOAT_DELTA);
356        assertEquals(0, event.userId);
357        assertFalse(event.nightMode);
358        assertEquals(1.0f, event.batteryLevel, FLOAT_DELTA);
359        assertEquals("com.example.app", event.packageName);
360        assertTrue(event.isDefaultBrightnessConfig);
361        assertEquals(0.5f, event.powerBrightnessFactor, FLOAT_DELTA);
362        assertTrue(event.isUserSetBrightness);
363
364        events = tracker.getEvents(1, true).getList();
365        assertEquals(1, events.size());
366        event = events.get(0);
367        assertEquals(someTimeAgo, event.timeStamp);
368        assertEquals(71, event.brightness, FLOAT_DELTA);
369        assertArrayEquals(new float[] {132.2f, 131.1f}, event.luxValues, FLOAT_DELTA);
370        assertArrayEquals(new long[] {someTimeAgo, someTimeAgo}, event.luxTimestamps);
371        assertEquals(32, event.lastBrightness, FLOAT_DELTA);
372        assertEquals(1, event.userId);
373        assertTrue(event.nightMode);
374        assertEquals(3235, event.colorTemperature);
375        assertEquals(0.5f, event.batteryLevel, FLOAT_DELTA);
376        assertEquals("com.android.anapp", event.packageName);
377        // Not present in the event so default to false.
378        assertFalse(event.isDefaultBrightnessConfig);
379        assertEquals(1.0, event.powerBrightnessFactor, FLOAT_DELTA);
380        assertFalse(event.isUserSetBrightness);
381
382        // Pretend user 1 is a profile of user 0.
383        mInjector.mProfiles = new int[]{0, 1};
384        events = tracker.getEvents(0, true).getList();
385        // Both events should now be returned.
386        assertEquals(2, events.size());
387        BrightnessChangeEvent userZeroEvent;
388        BrightnessChangeEvent userOneEvent;
389        if (events.get(0).userId == 0) {
390            userZeroEvent = events.get(0);
391            userOneEvent = events.get(1);
392        } else {
393            userZeroEvent = events.get(1);
394            userOneEvent = events.get(0);
395        }
396        assertEquals(0, userZeroEvent.userId);
397        assertEquals("com.example.app", userZeroEvent.packageName);
398        assertEquals(1, userOneEvent.userId);
399        // Events from user 1 should have the package name redacted
400        assertNull(userOneEvent.packageName);
401    }
402
403    @Test
404    public void testFailedRead() {
405        String someTimeAgo =
406                Long.toString(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(12));
407        mInjector.mCurrentTimeMillis = System.currentTimeMillis();
408
409        BrightnessTracker tracker = new BrightnessTracker(InstrumentationRegistry.getContext(),
410                mInjector);
411        String eventFile = "junk in the file";
412        try {
413            tracker.readEventsLocked(getInputStream(eventFile));
414        } catch (IOException e) {
415            // Expected;
416        }
417        assertEquals(0, tracker.getEvents(0, true).getList().size());
418
419        // Missing lux value.
420        eventFile =
421                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
422                        + "<events>\n"
423                        + "<event nits=\"194\" timestamp=\"" + someTimeAgo + "\" packageName=\""
424                        + "com.example.app\" user=\"10\" "
425                        + "batteryLevel=\"0.7\" nightMode=\"false\" colorTemperature=\"0\" />\n"
426                        + "</events>";
427        try {
428            tracker.readEventsLocked(getInputStream(eventFile));
429        } catch (IOException e) {
430            // Expected;
431        }
432        assertEquals(0, tracker.getEvents(0, true).getList().size());
433    }
434
435    @Test
436    public void testWriteThenRead() throws Exception {
437        final int brightness = 20;
438
439        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
440        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339);
441
442        startTracker(mTracker);
443        mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
444                batteryChangeEvent(30, 100));
445        mInjector.mSensorListener.onSensorChanged(createSensorEvent(2000.0f));
446        final long firstSensorTime = mInjector.currentTimeMillis();
447        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
448        mInjector.mSensorListener.onSensorChanged(createSensorEvent(3000.0f));
449        final long secondSensorTime = mInjector.currentTimeMillis();
450        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(3));
451        notifyBrightnessChanged(mTracker, brightness, true /*userInitiated*/,
452                0.5f /*powerBrightnessFactor*/, true /*hasUserBrightnessPoints*/,
453                false /*isDefaultBrightnessConfig*/);
454        ByteArrayOutputStream baos = new ByteArrayOutputStream();
455        mTracker.writeEventsLocked(baos);
456        mTracker.stop();
457
458        baos.flush();
459        ByteArrayInputStream input = new ByteArrayInputStream(baos.toByteArray());
460        BrightnessTracker tracker = new BrightnessTracker(InstrumentationRegistry.getContext(),
461                mInjector);
462        tracker.readEventsLocked(input);
463        List<BrightnessChangeEvent> events = tracker.getEvents(0, true).getList();
464
465        assertEquals(1, events.size());
466        BrightnessChangeEvent event = events.get(0);
467        assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, FLOAT_DELTA);
468        assertArrayEquals(new long[] {firstSensorTime, secondSensorTime}, event.luxTimestamps);
469        assertEquals(brightness, event.brightness, FLOAT_DELTA);
470        assertEquals(0.3, event.batteryLevel, FLOAT_DELTA);
471        assertTrue(event.nightMode);
472        assertEquals(3339, event.colorTemperature);
473        assertEquals(0.5f, event.powerBrightnessFactor, FLOAT_DELTA);
474        assertTrue(event.isUserSetBrightness);
475        assertFalse(event.isDefaultBrightnessConfig);
476    }
477
478    @Test
479    public void testWritePrunesOldEvents() throws Exception {
480        final int brightness = 20;
481
482        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
483        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339);
484
485        startTracker(mTracker);
486        mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
487                batteryChangeEvent(30, 100));
488        mInjector.mSensorListener.onSensorChanged(createSensorEvent(1000.0f));
489        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1));
490        mInjector.mSensorListener.onSensorChanged(createSensorEvent(2000.0f));
491        final long sensorTime = mInjector.currentTimeMillis();
492        notifyBrightnessChanged(mTracker, brightness);
493
494        // 31 days later
495        mInjector.incrementTime(TimeUnit.DAYS.toMillis(31));
496        mInjector.mSensorListener.onSensorChanged(createSensorEvent(3000.0f));
497        notifyBrightnessChanged(mTracker, brightness);
498        final long eventTime = mInjector.currentTimeMillis();
499
500        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
501        assertEquals(2, events.size());
502
503        ByteArrayOutputStream baos = new ByteArrayOutputStream();
504        mTracker.writeEventsLocked(baos);
505        events = mTracker.getEvents(0, true).getList();
506        mTracker.stop();
507
508        assertEquals(1, events.size());
509        BrightnessChangeEvent event = events.get(0);
510        assertEquals(eventTime, event.timeStamp);
511
512        // We will keep one of the old sensor events because we keep 1 event outside the window.
513        assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, FLOAT_DELTA);
514        assertArrayEquals(new long[] {sensorTime, eventTime}, event.luxTimestamps);
515        assertEquals(brightness, event.brightness, FLOAT_DELTA);
516        assertEquals(0.3, event.batteryLevel, FLOAT_DELTA);
517        assertTrue(event.nightMode);
518        assertEquals(3339, event.colorTemperature);
519    }
520
521    @Test
522    public void testParcelUnParcel() {
523        Parcel parcel = Parcel.obtain();
524        BrightnessChangeEvent.Builder builder = new BrightnessChangeEvent.Builder();
525        builder.setBrightness(23f);
526        builder.setTimeStamp(345L);
527        builder.setPackageName("com.example");
528        builder.setUserId(12);
529        float[] luxValues = new float[2];
530        luxValues[0] = 3000.0f;
531        luxValues[1] = 4000.0f;
532        builder.setLuxValues(luxValues);
533        long[] luxTimestamps = new long[2];
534        luxTimestamps[0] = 325L;
535        luxTimestamps[1] = 315L;
536        builder.setLuxTimestamps(luxTimestamps);
537        builder.setBatteryLevel(0.7f);
538        builder.setNightMode(false);
539        builder.setColorTemperature(345);
540        builder.setLastBrightness(50f);
541        BrightnessChangeEvent event = builder.build();
542
543        event.writeToParcel(parcel, 0);
544        byte[] parceled = parcel.marshall();
545        parcel.recycle();
546
547        parcel = Parcel.obtain();
548        parcel.unmarshall(parceled, 0, parceled.length);
549        parcel.setDataPosition(0);
550
551        BrightnessChangeEvent event2 = BrightnessChangeEvent.CREATOR.createFromParcel(parcel);
552        parcel.recycle();
553        assertEquals(event.brightness, event2.brightness, FLOAT_DELTA);
554        assertEquals(event.timeStamp, event2.timeStamp);
555        assertEquals(event.packageName, event2.packageName);
556        assertEquals(event.userId, event2.userId);
557        assertArrayEquals(event.luxValues, event2.luxValues, FLOAT_DELTA);
558        assertArrayEquals(event.luxTimestamps, event2.luxTimestamps);
559        assertEquals(event.batteryLevel, event2.batteryLevel, FLOAT_DELTA);
560        assertEquals(event.nightMode, event2.nightMode);
561        assertEquals(event.colorTemperature, event2.colorTemperature);
562        assertEquals(event.lastBrightness, event2.lastBrightness, FLOAT_DELTA);
563
564        parcel = Parcel.obtain();
565        builder.setBatteryLevel(Float.NaN);
566        event = builder.build();
567        event.writeToParcel(parcel, 0);
568        parceled = parcel.marshall();
569        parcel.recycle();
570
571        parcel = Parcel.obtain();
572        parcel.unmarshall(parceled, 0, parceled.length);
573        parcel.setDataPosition(0);
574        event2 = BrightnessChangeEvent.CREATOR.createFromParcel(parcel);
575        assertEquals(event.batteryLevel, event2.batteryLevel, FLOAT_DELTA);
576    }
577
578    @Test
579    public void testNonNullAmbientStats() {
580        // getAmbientBrightnessStats should return an empty list rather than null when
581        // tracker isn't started or hasn't collected any data.
582        ParceledListSlice<AmbientBrightnessDayStats> slice = mTracker.getAmbientBrightnessStats(0);
583        assertNotNull(slice);
584        assertTrue(slice.getList().isEmpty());
585        startTracker(mTracker);
586        slice = mTracker.getAmbientBrightnessStats(0);
587        assertNotNull(slice);
588        assertTrue(slice.getList().isEmpty());
589    }
590
591    @Test
592    public void testBackgroundHandlerDelay() {
593        final int brightness = 20;
594
595        // Setup tracker.
596        startTracker(mTracker);
597        mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f));
598        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
599
600        // Block handler from running.
601        final CountDownLatch latch = new CountDownLatch(1);
602        mInjector.mHandler.post(
603                () -> {
604                    try {
605                        latch.await();
606                    } catch (InterruptedException e) {
607                        fail(e.getMessage());
608                    }
609                });
610
611        // Send an event.
612        long eventTime = mInjector.currentTimeMillis();
613        mTracker.notifyBrightnessChanged(brightness, true /*userInitiated*/,
614                1.0f /*powerBrightnessFactor*/, false /*isUserSetBrightness*/,
615                false /*isDefaultBrightnessConfig*/);
616
617        // Time passes before handler can run.
618        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
619
620        // Let the handler run.
621        latch.countDown();
622        mInjector.waitForHandler();
623
624        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
625        mTracker.stop();
626
627        // Check event was recorded with time it was sent rather than handler ran.
628        assertEquals(1, events.size());
629        BrightnessChangeEvent event = events.get(0);
630        assertEquals(eventTime, event.timeStamp);
631    }
632
633    private InputStream getInputStream(String data) {
634        return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
635    }
636
637    private Intent batteryChangeEvent(int level, int scale) {
638        Intent intent = new Intent();
639        intent.setAction(Intent.ACTION_BATTERY_CHANGED);
640        intent.putExtra(BatteryManager.EXTRA_LEVEL, level);
641        intent.putExtra(BatteryManager.EXTRA_SCALE, scale);
642        return intent;
643    }
644
645    private SensorEvent createSensorEvent(float lux) {
646        SensorEvent event;
647        try {
648            Constructor<SensorEvent> constr =
649                    SensorEvent.class.getDeclaredConstructor(Integer.TYPE);
650            constr.setAccessible(true);
651            event = constr.newInstance(1);
652        } catch (Exception e) {
653            throw new RuntimeException(e);
654        }
655        event.values[0] = lux;
656        event.timestamp = mInjector.mElapsedRealtimeNanos;
657
658        return event;
659    }
660
661    private void startTracker(BrightnessTracker tracker) {
662        startTracker(tracker, DEFAULT_INITIAL_BRIGHTNESS);
663    }
664
665    private void startTracker(BrightnessTracker tracker, float initialBrightness) {
666        tracker.start(initialBrightness);
667        mInjector.waitForHandler();
668    }
669
670    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness) {
671        notifyBrightnessChanged(tracker, brightness, true /*userInitiated*/,
672                1.0f /*powerBrightnessFactor*/, false /*isUserSetBrightness*/,
673                false /*isDefaultBrightnessConfig*/);
674    }
675
676    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
677            boolean userInitiated, float powerBrightnessFactor, boolean isUserSetBrightness,
678            boolean isDefaultBrightnessConfig) {
679        tracker.notifyBrightnessChanged(brightness, userInitiated, powerBrightnessFactor,
680                isUserSetBrightness, isDefaultBrightnessConfig);
681        mInjector.waitForHandler();
682    }
683
684    private static final class Idle implements MessageQueue.IdleHandler {
685        private boolean mIdle;
686
687        @Override
688        public boolean queueIdle() {
689            synchronized (this) {
690                mIdle = true;
691                notifyAll();
692            }
693            return false;
694        }
695
696        public synchronized void waitForIdle() {
697            while (!mIdle) {
698                try {
699                    wait();
700                } catch (InterruptedException e) {
701                }
702            }
703        }
704    }
705
706    private class TestInjector extends BrightnessTracker.Injector {
707        SensorEventListener mSensorListener;
708        BroadcastReceiver mBroadcastReceiver;
709        Map<String, Integer> mSecureIntSettings = new HashMap<>();
710        long mCurrentTimeMillis = System.currentTimeMillis();
711        long mElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
712        Handler mHandler;
713        boolean mIdleScheduled;
714        boolean mInteractive = true;
715        int[] mProfiles;
716        ContentObserver mContentObserver;
717        boolean mIsBrightnessModeAutomatic = true;
718
719        public TestInjector(Handler handler) {
720            mHandler = handler;
721        }
722
723        void incrementTime(long timeMillis) {
724            mCurrentTimeMillis += timeMillis;
725            mElapsedRealtimeNanos += TimeUnit.MILLISECONDS.toNanos(timeMillis);
726        }
727
728        void setBrightnessMode(boolean isBrightnessModeAutomatic) {
729          mIsBrightnessModeAutomatic = isBrightnessModeAutomatic;
730          mContentObserver.dispatchChange(false, null);
731          waitForHandler();
732        }
733
734        void sendScreenChange(boolean screenOn) {
735            mInteractive = screenOn;
736            Intent intent = new Intent();
737            intent.setAction(screenOn ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF);
738            mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(), intent);
739            waitForHandler();
740        }
741
742        void waitForHandler() {
743            Idle idle = new Idle();
744            mHandler.getLooper().getQueue().addIdleHandler(idle);
745            mHandler.post(() -> {});
746            idle.waitForIdle();
747        }
748
749        @Override
750        public void registerSensorListener(Context context,
751                SensorEventListener sensorListener, Handler handler) {
752            mSensorListener = sensorListener;
753        }
754
755        @Override
756        public void unregisterSensorListener(Context context,
757                SensorEventListener sensorListener) {
758            mSensorListener = null;
759        }
760
761        @Override
762        public void registerBrightnessModeObserver(ContentResolver resolver,
763                ContentObserver settingsObserver) {
764            mContentObserver = settingsObserver;
765        }
766
767        @Override
768        public void unregisterBrightnessModeObserver(Context context,
769                ContentObserver settingsObserver) {
770            mContentObserver = null;
771        }
772
773        @Override
774        public void registerReceiver(Context context,
775                BroadcastReceiver shutdownReceiver, IntentFilter shutdownFilter) {
776            mBroadcastReceiver = shutdownReceiver;
777        }
778
779        @Override
780        public void unregisterReceiver(Context context,
781                BroadcastReceiver broadcastReceiver) {
782            assertEquals(mBroadcastReceiver, broadcastReceiver);
783            mBroadcastReceiver = null;
784        }
785
786        @Override
787        public Handler getBackgroundHandler() {
788            return mHandler;
789        }
790
791        @Override
792        public boolean isBrightnessModeAutomatic(ContentResolver resolver) {
793            return mIsBrightnessModeAutomatic;
794        }
795
796        @Override
797        public int getSecureIntForUser(ContentResolver resolver, String setting, int defaultValue,
798                int userId) {
799            Integer value = mSecureIntSettings.get(setting);
800            if (value == null) {
801                return defaultValue;
802            } else {
803                return value;
804            }
805        }
806
807        @Override
808        public AtomicFile getFile(String filename) {
809            // Don't have the test write / read from anywhere.
810            return null;
811        }
812
813        @Override
814        public long currentTimeMillis() {
815            return mCurrentTimeMillis;
816        }
817
818        @Override
819        public long elapsedRealtimeNanos() {
820            return mElapsedRealtimeNanos;
821        }
822
823        @Override
824        public int getUserSerialNumber(UserManager userManager, int userId) {
825            return userId + 10;
826        }
827
828        @Override
829        public int getUserId(UserManager userManager, int userSerialNumber) {
830            return userSerialNumber - 10;
831        }
832
833        @Override
834        public int[] getProfileIds(UserManager userManager, int userId) {
835            if (mProfiles != null) {
836                return mProfiles;
837            } else {
838                return new int[]{userId};
839            }
840        }
841
842        @Override
843        public ActivityManager.StackInfo getFocusedStack() throws RemoteException {
844            ActivityManager.StackInfo focusedStack = new ActivityManager.StackInfo();
845            focusedStack.userId = 0;
846            focusedStack.topActivity = new ComponentName("a.package", "a.class");
847            return focusedStack;
848        }
849
850        @Override
851        public void scheduleIdleJob(Context context) {
852            // Don't actually schedule jobs during unit tests.
853            mIdleScheduled = true;
854        }
855
856        @Override
857        public void cancelIdleJob(Context context) {
858            mIdleScheduled = false;
859        }
860
861        @Override
862        public boolean isInteractive(Context context) {
863            return mInteractive;
864        }
865    }
866}
867