1 /*
2  * Copyright 2022 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 
17 package com.android.server.display;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.mockito.Mockito.eq;
21 import static org.mockito.Mockito.isA;
22 import static org.mockito.Mockito.spy;
23 import static org.mockito.Mockito.times;
24 import static org.mockito.Mockito.verify;
25 import static org.mockito.Mockito.when;
26 
27 import android.content.Context;
28 import android.content.ContextWrapper;
29 import android.database.ContentObserver;
30 import android.hardware.display.DisplayManager;
31 import android.hardware.display.DisplayManager.DisplayListener;
32 import android.net.Uri;
33 import android.os.Handler;
34 import android.os.UserHandle;
35 import android.os.test.TestLooper;
36 import android.provider.Settings;
37 import android.test.mock.MockContentResolver;
38 import android.view.Display;
39 
40 import androidx.test.core.app.ApplicationProvider;
41 import androidx.test.filters.SmallTest;
42 import androidx.test.runner.AndroidJUnit4;
43 
44 import com.android.internal.display.BrightnessSynchronizer;
45 import com.android.internal.util.test.FakeSettingsProvider;
46 import com.android.server.testutils.OffsettableClock;
47 
48 import org.junit.Before;
49 import org.junit.Test;
50 import org.junit.runner.RunWith;
51 import org.mockito.ArgumentCaptor;
52 import org.mockito.Captor;
53 import org.mockito.Mock;
54 import org.mockito.MockitoAnnotations;
55 
56 @SmallTest
57 @RunWith(AndroidJUnit4.class)
58 public class BrightnessSynchronizerTest {
59     private static final float EPSILON = 0.00001f;
60     private static final Uri BRIGHTNESS_URI =
61             Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
62 
63     private Context mContext;
64     private MockContentResolver mContentResolverSpy;
65     private OffsettableClock mClock;
66     private DisplayListener mDisplayListener;
67     private ContentObserver mContentObserver;
68     private TestLooper mTestLooper;
69 
70     @Mock private DisplayManager mDisplayManagerMock;
71     @Captor private ArgumentCaptor<DisplayListener> mDisplayListenerCaptor;
72     @Captor private ArgumentCaptor<ContentObserver> mContentObserverCaptor;
73 
74     @Before
setUp()75     public void setUp() throws Exception {
76         MockitoAnnotations.initMocks(this);
77         mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
78         mContentResolverSpy = spy(new MockContentResolver(mContext));
79         mContentResolverSpy.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
80         when(mContext.getContentResolver()).thenReturn(mContentResolverSpy);
81         when(mContext.getSystemService(DisplayManager.class)).thenReturn(mDisplayManagerMock);
82         mClock = new OffsettableClock.Stopped();
83         mTestLooper = new TestLooper(mClock::now);
84     }
85 
86     @Test
testSetFloat()87     public void testSetFloat() throws Exception {
88         putFloatSetting(0.5f);
89         putIntSetting(128);
90         start();
91 
92         // Set float brightness to 0.4
93         putFloatSetting(0.4f);
94         advanceTime(10);
95         verifyIntWasSetTo(fToI(0.4f));
96     }
97 
98     @Test
testSetInt()99     public void testSetInt() {
100         putFloatSetting(0.5f);
101         putIntSetting(128);
102         start();
103 
104         // Set int brightness to 64
105         putIntSetting(64);
106         advanceTime(10);
107         verifyFloatWasSetTo(iToF(64));
108     }
109 
110     @Test
testSetIntQuickSuccession()111     public void testSetIntQuickSuccession() {
112         putFloatSetting(0.5f);
113         putIntSetting(128);
114         start();
115 
116         putIntSetting(50);
117         putIntSetting(40);
118         advanceTime(10);
119 
120         verifyFloatWasSetTo(iToF(50));
121 
122         // now confirm the first value (via callback) so that we can process the second one.
123         putFloatSetting(iToF(50));
124         advanceTime(10);
125         verifyFloatWasSetTo(iToF(40));
126     }
127 
128     @Test
testSetSameIntValue_nothingUpdated()129     public void testSetSameIntValue_nothingUpdated() {
130         putFloatSetting(0.5f);
131         putIntSetting(128);
132         start();
133 
134         putIntSetting(128);
135         advanceTime(10);
136         verify(mDisplayManagerMock, times(0)).setBrightness(
137                 eq(Display.DEFAULT_DISPLAY), eq(iToF(128)));
138     }
139 
140     @Test
testUpdateDuringResponseIsNotOverwritten()141     public void testUpdateDuringResponseIsNotOverwritten() {
142         putFloatSetting(0.5f);
143         putIntSetting(128);
144         start();
145 
146         // First, change the float to 0.4f
147         putFloatSetting(0.4f);
148         advanceTime(10);
149 
150         // Now set the int to something else (not equal to 0.4f)
151         putIntSetting(20);
152         advanceTime(10);
153 
154         // Verify that this update did not get sent to float, because synchronizer
155         // is still waiting for confirmation of its first value.
156         verify(mDisplayManagerMock, times(0)).setBrightness(
157                 eq(Display.DEFAULT_DISPLAY), eq(iToF(20)));
158 
159         // Send the confirmation of the initial change. This should trigger the new value to
160         // finally be processed and we can verify that the new value (20) is sent.
161         putIntSetting(fToI(0.4f));
162         advanceTime(10);
163         verify(mDisplayManagerMock).setBrightness(
164                 eq(Display.DEFAULT_DISPLAY), eq(iToF(20)));
165 
166     }
167 
168     @Test
testSetFloat_outOfTimeForResponse()169     public void testSetFloat_outOfTimeForResponse() {
170         putFloatSetting(0.5f);
171         putIntSetting(128);
172         start();
173         advanceTime(210);
174 
175         // First, change the float to 0.4f
176         putFloatSetting(0.4f);
177         advanceTime(10);
178 
179         // Now set the int to something else (not equal to 0.4f)
180         putIntSetting(20);
181 
182         // Now, go beyond the timeout so that the last 20 event gets executed.
183         advanceTime(200);
184 
185         // Verify that the new value gets sent because the timeout expired.
186         verify(mDisplayManagerMock).setBrightness(
187                 eq(Display.DEFAULT_DISPLAY), eq(iToF(20)));
188 
189         // Send a confirmation of the initial event, BrightnessSynchronizer should treat this as a
190         // new event because the timeout had already expired
191         putIntSetting(fToI(0.4f));
192         // Because the previous setting will be treated as a new event, we actually want to send
193         // confirmation of the setBrightness() we just verified so that it can be executed as well.
194         putFloatSetting(iToF(20));
195         advanceTime(10);
196 
197         // Verify we sent what would have been the confirmation as a new event to displaymanager.
198         // We do both fToI and iToF because the conversions are not symmetric.
199         verify(mDisplayManagerMock).setBrightness(
200                 eq(Display.DEFAULT_DISPLAY), eq(iToF(fToI(0.4f))));
201     }
202 
start()203     private BrightnessSynchronizer start() {
204         BrightnessSynchronizer bs = new BrightnessSynchronizer(mContext, mTestLooper.getLooper(),
205                 mClock::now);
206         bs.startSynchronizing();
207         verify(mDisplayManagerMock).registerDisplayListener(mDisplayListenerCaptor.capture(),
208                 isA(Handler.class), eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
209         mDisplayListener = mDisplayListenerCaptor.getValue();
210 
211         verify(mContentResolverSpy).registerContentObserver(eq(BRIGHTNESS_URI), eq(false),
212                 mContentObserverCaptor.capture(), eq(UserHandle.USER_ALL));
213         mContentObserver = mContentObserverCaptor.getValue();
214         return bs;
215     }
216 
getIntSetting()217     private int getIntSetting() throws Exception {
218         return Settings.System.getInt(mContentResolverSpy, Settings.System.SCREEN_BRIGHTNESS);
219     }
220 
putIntSetting(int brightness)221     private void putIntSetting(int brightness) {
222         Settings.System.putInt(mContentResolverSpy, Settings.System.SCREEN_BRIGHTNESS, brightness);
223         if (mContentObserver != null) {
224             mContentObserver.onChange(false /*=selfChange*/, BRIGHTNESS_URI);
225         }
226     }
227 
putFloatSetting(float brightness)228     private void putFloatSetting(float brightness) {
229         when(mDisplayManagerMock.getBrightness(eq(Display.DEFAULT_DISPLAY))).thenReturn(brightness);
230         if (mDisplayListener != null) {
231             mDisplayListener.onDisplayChanged(Display.DEFAULT_DISPLAY);
232         }
233     }
234 
verifyIntWasSetTo(int brightness)235     private void verifyIntWasSetTo(int brightness) throws Exception {
236         assertEquals(brightness, getIntSetting());
237     }
238 
verifyFloatWasSetTo(float brightness)239     private void verifyFloatWasSetTo(float brightness) {
240         verify(mDisplayManagerMock).setBrightness(eq(Display.DEFAULT_DISPLAY), eq(brightness));
241     }
242 
fToI(float brightness)243     private int fToI(float brightness) {
244         return BrightnessSynchronizer.brightnessFloatToInt(brightness);
245     }
246 
iToF(int brightness)247     private float iToF(int brightness) {
248         return BrightnessSynchronizer.brightnessIntToFloat(brightness);
249     }
250 
advanceTime(long timeMs)251     private void advanceTime(long timeMs) {
252         mClock.fastForward(timeMs);
253         mTestLooper.dispatchAll();
254     }
255 }
256